Scripts and XML code
Naming scripts
HINWEIS
In the following, "name of a script" always means the Id.
Names of scripts must be set thoughtfully, as they serve as a unique identifier. Once a script is in a template, changing the name causes the script to having to be reinserted.
At best, script names describe the output. For example, "LocationDate" makes more sense than "LetterStart."
Script names should also be as simple and general as possible. For example, "Author" makes more sense than "ReportAuthor".
Often, however, the output cannot be described because it may be completely changed by specific conditions. In this case, the location of the script in the document may be set as the name (e.g. "Footer1stPage").
Script names are formatted using "Pascal Casing". This means that the initial letter and each word-initial letter are always capitalized.
Examples:
ContactInformation
LocationDate
Script names must not contain umlauts (äöü) and special characters except "_" (underline). Dots are used for classification in the folder structure.
Script names are limited to 100 characters.
The development language in OneOffixx is basically English, both in the source code and in the scripts. Comments are an exception: there you may also write in German. Comments start with the characters <!-- and end with -->.
Global Configurations
As global as possible
In most cases, the more global the better. This means: it is better to outsource a script too much than one too little. As soon as a script occurs more than once, it is swapped out (added to Global configurations and linked) so that the script only needs to be maintained in one place.
If you need a new script to be slightly different from an existing script, you accomplish this in three ways:
1. Extend existing script
The existing script can be modified to create the desired difference on a condition. The condition could, for example, query the state of a non-visible document parameter checkbox (e.g. DocParam.EnableDateOfInHeader) or rely on the contents of a specific script name (e.g. CustomElements.EnableDateOfInHeader = 'true'). Example:
<CustomDataNode id="HeaderAdditionalText">
<Line>
<Element id="Org.Unit" />
</Line>
<Line>
<Text when="DocParam.EnableDateOfInHeader">{D[Texts.DateOf]} </Text>
<Element id="DocParam.Date" fFormatingDate="{D[Configuration.DateFormat.WrittenOut]}" />
</Line>
</CustomDataNode>
Here (next to the department on the 1st line) either "from January 1, 2017" or "January 1, 2017" is output, depending on whether DocParam.EnableDateOfInHeader
is enabled in a template or not.
✔ Maintainability – Since no additional script was created, it is easier to keep track of scripts and their functionalities later.
❌ Complexity – Scripts with conditions are more demanding to read and edit.
2. Use inheritance
If a script is either written in the layout template or linked to the Global Configurations, it is also possible to copy this script to the content template and change the content there. Then the script content is consequently controlled by the main template. In this case, the name must remain the same. The document pipeline then goes through all scripts during generation and will then take the lowest version of the script in the chain (in this case in the content template). Of course, this also works from layout template to function template or from content template to function template.
✔ Simplicity – The script is simply copied and pasted into the layout template based template in the template hierarchy and adjusted. The process is very simple and straightforward.
✔ Maintainability – If the global script is to be used again, the overwriting script is simply deleted or commented out.
❌ Complexity – For other template editors it can be difficult to find the right script they see in the Word template. The reason for this is that the Id is the same (which we also take advantage of with inheritance). They need to know that the generation is effectively not accessing the global script but an overriding version in the content or function template.
3. Create a copy
In case of emergency, you can copy the script, adjust the name of the script and make the changes to the copy. Accordingly, this new script must be reinserted in the template. The difference from the other script should be clearly visible (for example, a comment can be entered. At best, one sees the difference in the name).
✔ Faster – Creating copies of scripts is easier and goes faster.
❌ Maintainability – with many similar scripts, it is easy to lose track of them. This especially if the differences of the similar scripts are not apparent.
Structure in the Global Configurations
Groups
New scripts are stored in the Global Configurations in the "Scripts" group for practically all customer solutions. In addition to scripts, other configurations that are frequently used can also be stored in the Global Configurations, such as regex validations, HTML modules or Formatting document function configurations. However, the latter should always be attached directly to the layout or content template if possible. Such scripts then do not belong in the "Scripts" group, but in groups newly created for this purpose. For example, Formatting document function configurations belong in the "Formatting" group.
Names of configuration parts and scripts
In principle, individual entries in the Global Configurations have exactly the same name as the CustomDataNodes they contain when swapped out. In the following example, the group "Scripts" and the entry "Salutation" is selected. The CustomDataNode is also named "Salutation":
Gruppe Scripts.Salutation: entry
Group Scripts.Salutation: XML
<CustomDataNode id="Salutation">
<Line>
<Element id="Contact.Recipient.Selected.Person.Salutation" textafter="{D[Configuration.SalutationSuffix]}" />
</Line>
<Line>
<Element id="Contact.Recipient.Selected.AdditionalPerson.Salutation" textafter="{D[Configuration.SalutationSuffix]}" />
</Line>
</CustomDataNode>
However, often different scripts are needed for a template group or for a document type (contracts, letters, certificates of employment, etc.). In this case, these scripts are all stored in one entry. The names of the individual CustomDataNodes are then according to the scheme: http://Entry.Name . In the following case it is Signers.Signer_0.
Gruppe Scripts.Signers: entry
Group Scripts.Signers: XML
<CustomDataNode id="Signers.Signer_0.NameLine">
<Element id="Signer_0.User.Title" separator=" "/>
<Element id="Signer_0.User.FirstName" separator=" " />
<Element id="Signer_0.User.LastName" />
</CustomDataNode>
<CustomDataNode id="Signers.Signer_0.Function">
<Element id="Signer_0.User.Function" />
</CustomDataNode>
<CustomDataNode id="Signers.Signer_1.NameLine">
<Element id="Signer_1.User.Title" separator=" " />
<Element id="Signer_1.User.FirstName" separator=" " />
<Element id="Signer_1.User.LastName" />
</CustomDataNode>
<CustomDataNode id="Signers.Signer_1.Function">
<Element id="Signer_1.User.Function" />
</CustomDataNode>
This configuration is consequently integrated in the templates by {[Scripts.Signers]}
. At "Link Content" you can thus access the folder "Signers" which contains four scripts. The scripts from the Global Configurations are linked in the layout templates, if possible, so that all attached content templates also have these scripts available. The case where only one template requires a large number of scripts is relatively rare.
There is one case where it makes sense to number scripts, namely when, depending on the condition, different content is in a table and formatted differently. Thus, if a script must be divided into several partial scripts, this script is usually intended exclusively for this use. With this exception, it makes no sense to assign a descriptive name for each partial script.
Example: Suppose a table is created that is formatted bold or normal. The scripts can then be divided by Row and Column and then by bold and non-bold. This way you know exactly which script goes into which cell:
Protocol.Row1.Col1.bold
Protocol.Row1.Col2.normal
Protocol.Row2.Col1.bold
Protocol.Row2.Col2.normal
Protocol.Row3.Col1.bold
Protocol.Row3.Col2.normal
<!-- Table row 1 -->
<CustomDataNode id="Protocol.Row1.Col1.bold">
<Condition when="DocParam.RBInvitationProtocol='2'">
<Condition when="DocParam.MeetingDate">
<Line>
<Text>Sitzung</Text>
</Line>
</Condition>
</Condition>
</CustomDataNode>
<CustomDataNode id="Protocol.Row1.Col2.normal">
<Condition when="DocParam.RBInvitationProtocol='2'">
<Condition when="DocParam.MeetingDate">
<Line>
<Element id="DocParam.MeetingDate" separator=", " />
<Element id="DocParam.MeetingTime" textafter=" Uhr" />
</Line>
</Condition>
</Condition>
</CustomDataNode>
<!-- Table row 2 -->
<CustomDataNode id="Protocol.Row2.Col1.bold">
<Condition when="DocParam.RBInvitationProtocol='2'">
<Condition when="DocParam.MeetingPlace">
<Line>
<Text>Ort</Text>
</Line>
</Condition>
</Condition>
</CustomDataNode>
<CustomDataNode id="Protocol.Row2.Col2.normal">
<Condition when="DocParam.RBInvitationProtocol='2'">
<Condition when="DocParam.MeetingPlace">
<Line>
<Element id="DocParam.MeetingPlace" />
</Line>
</Condition>
</Condition>
</CustomDataNode>
<!-- Table row 3 -->
<CustomDataNode id="Protocol.Row3.Col1.bold">
<Condition when="DocParam.RBInvitationProtocol='2'">
<Condition when="DocParam.Chair">
<Line>
<Text>Chair</Text>
</Line>
</Condition>
</Condition>
</CustomDataNode>
<CustomDataNode id="Protocol.Row3.Col2.normal">
<!-- Protocol -->
<Condition when="DocParam.RBInvitationProtocol='2'">
<Condition when="DocParam.Chair">
<Line>
<Element id="DocParam.Chair" />
</Line>
</Condition>
</Condition>
</CustomDataNode>
For example, in Word, it would be like this:
Basically the name should be as descriptive as possible according to the chapter Naming scripts or in a few cases describe the position.
Recursion in scripts
Simple recursion
Sometimes it makes sense to access the result of another script in one script. If, for example, in five different scripts of the solution it has to be determined whether Profile and Signer_1 correspond to each other and whether the current organizational unit is not the Finance Department, this can be determined via a script whose result we access afterwards:
<Configuration>
<Script engine="XSL" version="2" depth="3">
<!-- ↓ is 'true' if profile and signer_1 are the same
and the OU is not the finance department at the same time -->
<CustomDataNode id="Logic.Signer1EqualsProfileAndOuIsNotFinance">
<Condition when="Profile.Id = Signer_1.Id">
<!-- d7427a29-dda3-4a22-b6ec-e97dba9a8c22 is the id of unit "finance department" ↓ -->
<Condition notwhen="Profile.OrganizationUnitId = 'd7427a29-dda3-4a22-b6ec-e97dba9a8c22'">
<Text>true</Text>
</Condition>
</Condition>
</CustomDataNode>
</Script>
</Configuration>
When linking global scripts that access other logic scripts, care must be taken to ensure that the logic script is also linked. Example, where "CompanyAddress" needs the result of the logic script: {[Scripts.Logic.Signer1EqualsProfileAndOuIsNotFinance]}
{[Scripts.CompanyAddress]}
Multiple recursion (Depth)
Accessing a result of a simple script always works. Accessing a result of a script which in turn accesses the result of another script is enabled with Depth
. This happens in the Scripts document function. Here the Depth is set to 5:
<Configuration>
<Script engine="XSL" version="2" depth="5">
This causes now that the script results are calculated 3 times and consequently that the access of a script A to the result of a script B, which accesses the result of a script C, works. The Depth is stored in each customer database in the Global Configurations as follows:
In each [Scripts document function](~/docfunc/docfunc-all/scripts.md) the link then looks like this:
<Configuration>
<Script engine="XSL" version="2" depth="{[Config.Depth]}">
If a result of a script accessing other scripts is not as expected, it is necessary to check,
whether the script being accessed is available / linked at all.
whether the Depth is at least as high as the number of nested accesses.
Code representation
An indent level consists of 2 spaces or a tab. When pressing the Tab key in the OneOffixx XML editor, 2 spaces are inserted. If multiple lines are selected, pressing "Tab" and "Shift+Tab" can increase and decrease the indent of the selected lines. The following is an example with correct indents:
<Configuration>
<Script engine="XSL" version="2" depth="{[Config.Depth]}">
<CustomDataNode id="EnclosuresBox">
<Line>
<Text>{D[Texts.Enclosures]}:</Text>
</Line>
<Line>
<Element id="DocParam.Enclosures" linePrefix="&#8211;&#009;" />
</Line>
</CustomDataNode>
</Script>
</Configuration>
Global Configurations
Nice code in the Global Configurations includes having an empty paragraph follow each "CustomDataNode" element in an entry. Here is an example:
<CustomDataNode id="CompanyAddress.IncludingUnit">
[...]
</CustomDataNode>
<CustomDataNode id="CompanyAddress.WithoutUnit">
[...]
</CustomDataNode>
<CustomDataNode id="CompanyPhone">
[...]
</CustomDataNode>
Scripts
CustomDataNodes are also visibly separated from each other by blank lines. Complicated scripts as well as conditions in the scripts should be explained in an introductory comment. For conditions, the value of the when/notwhen attribute can contain line breaks. These improve readability:
<Configuration>
<Script engine="XSL" version="2" depth="{[Config.Depth]}">
{[Scripts.CompanyAddress]}
{[Scripts.EnclosuresBox]}
<CustomDataNode id="InfoBox.Top">
<!-- At the top of the orange InfoBox appears the description from the document parameter
(DocParam.Description), if it is filled in. Otherwise a general information
(Texts.ContractInformation from Global Translations) appears,
if it is a proposal, a contract or an offer. -->
<Element id="DocParam.Description" />
<Condition notwhen="DocParam.Description">
<Condition when="DocParam.DocType = '{U[DocParam.DocType.Proposal]}' |
DocParam.DocType = '{U[DocParam.DocType.Contract]}' |
DocParam.DocType = '{U[DocParam.DocType.Offer]}'">
<!-- The text below will only appear if it is
a proposal, contract or offer -->
<Line>
<Text>{D[Texts.ContractInformation]}</Text>
</Line>
</Condition>
</Condition>
</CustomDataNode>
<CustomDataNode id="InfoBox.Bottom">
<!-- At the bottom of the orange InfoBox the version from the document parameter
(DocParam.Version) appears as well as a copyright information.-->
<Line>
<Text>{D[Texts.Version]} </Text>
<Element id="DocParam.Version" />
</Line>
<Line fixoutput="true" />
<Line>
<Text>&#169; </Text>
<Element id="DocParam.Date" fFormatingDate="yyyy" separator=" " />
<Element id="Profile.Org.Title" />
</Line>
</CustomDataNode>
<!-- Translations -->
<CustomDataNode id="Texts.Page">
<Text>{D[Texts.Page]}</Text>
</CustomDataNode>
<CustomDataNode id="Texts.PageOf">
<Text>{D[Texts.PageOf]}</Text>
</CustomDataNode>
</Script>
</Configuration>
Document parameters
By bringing attributes to the same height with tabs, clarity can be increased:
<Text Id="DocParam.Subject" />
<DateTime Id="DocParam.Date" />
<CheckBox Id="DocParam.Option01" />
<CheckBox Id="DocParam.Option02" />
<CheckBox Id="DocParam.Option03" />
<CheckBox Id="DocParam.Option04" />
<CheckBox Id="DocParam.Option05" />
<CheckBox Id="DocParam.Option06" />
<CheckBox Id="DocParam.Option07" />
<CheckBox Id="DocParam.Option08" />
<CheckBox Id="DocParam.Option09" />
<CheckBox Id="DocParam.Option10" />
<CheckBox Id="DocParam.Option11" />
<CheckBox Id="DocParam.Option12" />
<ComboBox Id="DocParam.Options" SelectedValue="2">
<Item Value="1" DisplayText="Option A" />
<Item Value="2" DisplayText="Option B" />
</ComboBox>
<Text Id="DocParam.Option12Text" />
<Text Id="DocParam.Enclosures" />
<Text Id="DocParam.CopyTo" />
PrimeSoft AG, Bahnhofstrasse 4, 8360 Eschlikon, Switzerland