XSLT cheatsheet

<xsl:value-of select="key('field-value', 'EANTAL')" />

<xsl:element name="input">
 <xsl:attribute name="id">AKTEST2</xsl:attribute>

<xsl:if test="/lxml:data/lxml:fields/lxml:field[@name='AKTEST2']/lxml:value = 'J'">
 <xsl:attribute name="checked" />
</xsl:if>

<xsl:choose>
  <xsl:when test="expression">
    ... some output ...
  </xsl:when>
  <xsl:otherwise>
    ... some output ....
  </xsl:otherwise>
</xsl:choose>
Advertisements

XLST sum

Being hired to work on an extensive, complex and most of all, very used system, I naturally have to jump between different sides of the system. Having added some features to an application I then got back to XSLT.

One of the things I had to do was to generate a sum. Now, I could have done this in Lansa, but it was more logically done in XSLT so I did that. Except, since I could not remember the exact syntax I did a search and found many convoluted examples. And it is actually quite easy:

<xsl:value-of select="sum(XPATH)" />

Specifically I used it in this context:

<xsl:template match="/lxml:data/lxml:lists/lxml:list[@name='ESTLIST']">
<xsl:variable name="thelist"
 select="/lxml:data/lxml:lists/lxml:list[@name='ESTLIST']" />
...
<xsl:for-each select="$thelist/lxml:list-entries/lxml:entry">
 <xsl:variable name="BUTNR" select="lxml:column[@name='X_BUTNR']" />
 <xsl:variable name="BUKORT" select="lxml:column[@name='X_BUKORT']" />
 <xsl:variable name="EANTAL" select="lxml:column[@name='X_EANTAL']" /
...
</xsl:for-each>
...
<xsl:value-of select="sum($thelist/lxml:list-entries/lxml:entry/lxml:column[@name='X_EANTAL'])" />
...
</xsl:template>

The XML source was essentially

<?xml version="1.0"?>
 <lxml:data xmlns:lxml="http://www.lansa.com/2002/XML/Runtime-Data">
 <lxml:lists>
 <lxml:list name="ESTLIST" row-count="3">
 <lxml:list-header>
 <lxml:header name="X_BUTNR">...</lxml:header>
 <lxml:header name="X_BUKORT">...</lxml:header>
 <lxml:header name="X_EANTAL">...</lxml:header>
 </lxml:list-header>
 <lxml:list-entries>
 <lxml:entry>
 <lxml:column name="X_BUTNR" id="ESTLIST.0001.X_BUTNR">ABCD</lxml:column>
 <lxml:column name="X_BUKORT" id="ESTLIST.0001.X_BUKORT">aAbBcCdDeEfFgGh</lxml:column>
 <lxml:column name="X_EANTAL" id="ESTLIST.0001.X_EANTAL">12345</lxml:column>
 </lxml:entry>
 <lxml:entry>
 <lxml:column name="X_BUTNR" id="ESTLIST.0002.X_BUTNR">ABCD</lxml:column>
 <lxml:column name="X_BUKORT" id="ESTLIST.0002.X_BUKORT">aAbBcCdDeEfFgGh</lxml:column>
 <lxml:column name="X_EANTAL" id="ESTLIST.0002.X_EANTAL">12345</lxml:column>
 </lxml:entry>
 <lxml:entry>
 <lxml:column name="X_BUTNR" id="ESTLIST.0003.X_BUTNR">ABCD</lxml:column>
 <lxml:column name="X_BUKORT" id="ESTLIST.0003.X_BUKORT">aAbBcCdDeEfFgGh</lxml:column>
 <lxml:column name="X_EANTAL" id="ESTLIST.0003.X_EANTAL">12345</lxml:column>
 </lxml:entry>
 </lxml:list-entries>
 </lxml:list>
 </lxml:lists>
 </lxml:data>

Overflow

One thing that surprised me, and made me waste a day, was that there is no check for overflow in numeric operations by default.

If I try to divide by zero, my program crashes and I even get a very precise explanation of why, but overflowing provides neither.

So, a field defined as Packed(2,0) with

Define Field(*HMJPCK2) Type(*PACKED) Length(2) Decimals(0) Default(0)

it can only hold values between -99 and 99, inclusive. That is not the surprise, the surprise comes from the missing error when going beyond these values. 99 + 10 will give me 9 and no error.

I am very tempted to write a function to handle simple arithmetics to give me the proper error messages but I am too new at this language to start doing that.

Functions

Back to Lansa, and functions. This stumped me at first but now I think I have got it.

Function Options(*DIRECT *NOMESSAGES) Rcv_List(#LIST)

#LIST is passed to the function which can then manipulate it. However, parameters are sent via the exchange stack:

Exchange Fields(#FIELD1 #FIELD2 #FIELD3 #FIELD4)
Call Process(*DIRECT) Function(FUNCNAME) Pass_Lst(#LIST)

The important point is that the fields and the list has to have exactly the same definition both places, which applies to all that is passed and exchanged. And since it is a list, that includes all the fields in the list, type, counter and not least number of entrys. So this would work:

Function:

Function Options(*DIRECT *NOMESSAGES) Rcv_List(#LIST)
Define Field(#FIELD1) Type(*CHAR) Length(10)
Define Field(#FIELD2) Type(*CHAR) Length(10)
Define Field(#FIELD3) Type(*DEC) Length(2)
Define Field(#FIELD4) Type(*DEC) Length(2)
Define Field(#COL1) Type(*DEC) Length(5) Decimals(0) Edit_Code(Z)
Define Field(#COL2) Type(*CHAR) Length(20)
Define Field(#COL3) Reffld(#COL2)
Def_List Name(#LIST) Fields(#COL1 #COL2 #COL3) Counter(#LISTCOUNT) Type(*WORKING) Entrys(102)
...
Return

Caller:

Define Field(#FIELD1) Type(*CHAR) Length(10)
Define Field(#FIELD2) Type(*CHAR) Length(10)
Define Field(#FIELD3) Type(*DEC) Length(2)
Define Field(#FIELD4) Type(*DEC) Length(2)
Define Field(#COL1) Type(*DEC) Length(5) Decimals(0) Edit_Code(Z)
Define Field(#COL2) Type(*CHAR) Length(20)
Define Field(#COL3) Reffld(#COL2)
Def_List Name(#LIST) Fields(#COL1 #COL2 #COL3) Counter(#LISTCOUNT) Type(*WORKING) Entrys(102)
Exchange Fields(#FIELD1 #FIELD2 #FIELD3 #FIELD4)
Call Process(*DIRECT) Function(FUNCNAME) Pass_Lst(#LIST)

Assumptions when dealing with decimals and thousand separators

I have enjoyed the lists of typical, and erroneous, assumptions we developers make with regards to dates and time, geography, names, addresses and genders. So I will here add a short list of erroneous assumptions commonly made about decimals and separators:

  1. The decimal point and the separator is globally the same.
    The wikipedia page for the decimal mark lists at least 11 different characters and combinations.
  2. The decimal point is a dot and the thousand separator is a comma.
    This is the default in programming, thanks to most development principles having originated in USA. But, alas, in most European countries it is exactly the opposite.
  3. At least, they are the same for every country.
    Except when they are not. Canada uses different rules depending on language.
  4. What about for all situations within the same language?
    No, sorry. The Swiss use one rule currency and what is practically the exact opposite for other numbers.
  5. I got it now. At least they are always found in the ASCII character set.
    Of course not, not even close. The Japanese prefer characters found in their own character set. Eastern Arabic numerals likewise.
  6. OK, then I can assume that thousand separators are for every 3 digits, right?
    Not even close. If the Chinese or Japanese group their digits they prefer 4 digits.
  7. The rules for printed and handwritten numbers be the same?
    Sorry, some countries have different traditions depending on form. Spain, Italy, Switzerland and others are the special cases here.
  8. If there is a rule for separators for every X digits, they always apply. Definitely.
    Well, even the Americans seem to get this one wrong, as 4 digit numbers are often written without a thousand separator, but 5-or-more digit numbers with. This is however nothing compared to the Indians who find it natural to but a separator with before the final 3 digits, but for every 2 digits before that.
  9. The separators are the same for all positions within a single number.
    Yes. Except of course if the Japanese or Chinese choose to use thousand, or rather, myriad, separators, which depend on position.

See Wikipedia:Decimal_mark, Wikipedia:Japanese_numerals, Wikipedia:Eastern_Arabic_NumeralsHow the world separates its digits and How the world separates its decimals.

XSLT

Working with Lansa, I also have to maintain some WAMs. This entails quite a bit of XSLT, so today I needed to produce a formatted number, except when the value was zero.

I ended up with this, which is supported by the XSLT parser:

<xsl:transform>
 ...
 <!-- decimal-format 'cause I am danish -->
 <xsl:decimal-format name="euro" decimal-separator="," grouping-separator="."
 NaN="" />
 ...
 <!-- sample call -->
 <xsl:call-template name="shoe_number2">
 <xsl:with-param name="numericvalue" select="$WPARDAG" />
 </xsl:call-template>
 ...
 <!-- templates -->
 <xsl:template name="shoe_number0">
 <xsl:param name="numericvalue" />
 <xsl:choose>
 <xsl:when test="$numericvalue = 0">
 </xsl:when>
 <xsl:otherwise>
 <xsl:value-of select="format-number(number(translate($numericvalue, ',', '.')), '###.###', 'euro')" />
 </xsl:otherwise>
 </xsl:choose>
 </xsl:template>
 <xsl:template name="shoe_number2">
 <xsl:param name="numericvalue" />
 <xsl:choose>
 <xsl:when test="$numericvalue = 0">
 </xsl:when>
 <xsl:otherwise>
 <xsl:value-of select="format-number(number(translate($numericvalue, ',', '.')), '###.###,00', 'euro')" />
 </xsl:otherwise>
 </xsl:choose>
 </xsl:template>
 <xsl:template name="shoe_index1">
 <xsl:param name="numericvalue" />
 <xsl:choose>
 <xsl:when test="$numericvalue = 0">
 </xsl:when>
 <xsl:otherwise>
 <xsl:value-of select="format-number(number(translate($numericvalue, ',', '.')), '#,0', 'euro')" />
 </xsl:otherwise>
 </xsl:choose>
 </xsl:template>
 <xsl:template name="shoe_index0">
 <xsl:param name="numericvalue" />
 <xsl:choose>
 <xsl:when test="$numericvalue = 0">
 </xsl:when>
 <xsl:otherwise>
 <xsl:value-of select="format-number(number(translate($numericvalue, ',', '.')), '#', 'euro')" />
 </xsl:otherwise>
 </xsl:choose>
 </xsl:template>
</xsl:transform>

Worked surprisingly well…

 

 


Subroutine

A basic subroutine is defined with

Subroutine Name(SUBNAME)
...
Endroutine

and called with

Execute Subroutine(SUBNAME)

So far, so good.

Of course, parameters can be defined

Subroutine Name(SUBNAME) Parms(PARAMETERS)

where PARAMETERS is a list of parameters, of either

PARAMNAME
(PARAMNAME *RECEIVED)
(PARAMNAME *RETURNED)
(PARAMNAME *BOTH)

*BOTH is default and means that the parameters is used in the call (“received”) and may be changed by the subroutine (“returned”). *RECEIVED means that it is read only and will not be returned into the caller’s parameter list. All parameters are required so the subroutine interface is rather strict.

The subroutine is then called with

Execute Subroutine(SUBNAME) With_Parms(PARAMETERS)

Local variables, sorry, fields, are defined with DEFINE. More on that, later.