XSLT 1.0 and upper/lowercasing

And today I again found out about the translate function in XSLT so I will store this here:

<xsl:variable name="lowercaseselect="'abcdefghijklmnopqrstuvwxyzàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿžšœ'" />
<xsl:variable name="uppercaseselect="'ABCDEFGHIJKLMNOPQRSTUVWXYZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞŸŽŠŒ'" />
<xsl:value-of select="translate(/lxml:data/lxml:lists/lxml:list[@name='LSTFORP']/lxml:list-entries/lxml:entry[2]/lxml:column[@name='WFPLTEKST'], $uppercase, $lowercase)" />
This does two things: It finds the value of the WFPLTEKST field in the 2nd entry in the Lansa list LSTFORP and changes the case from upper to lower.
Advertisements

ROUND_UP – no, I don’t think so

The documentation for CHANGE’s ROUND_UP parameter states, among other things, that

If the intermediate work fields involved do not have at least one more decimal position than the field nominated in the FIELD parameter, then the ROUND_UP parameter will be ignored and treated as if ROUND_UP(*NO) was specified.

I have difficulty finding documentation on how intermediate work fields are created, if no PRECISION parameter is given, but there are a few implied hints in the documentation for PRECISION.

So, in my concrete example, I have one 9,0 number divided by a 10,0 number. The documentation implies the intermediate work fields will be 10,0. My result field is a 3,0 number.

The actual numbers are 122,682,586 and 1,000,000, respectively. I really would like to get 123 as the result in this particular case.

Just doing a

Change Field(#RESULT) To('#BIG / #SMALL')

gives me 122. Doing

Change Field(#RESULT) To('#BIG / #SMALL') Round_Up(*YES)

gives me 123. Great, but not something that ought to have worked. Doing the right thing,

Change Field(#RESULT) To('#BIG / #SMALL') Precision(11 1) Round_Up(*YES)

also gives me 123. And, though the parameter is called ROUND_UP, it actually does the “half adjust” which I found a description of that seems like it would do the right thing.


Note: Embarrassingly, I was under the impression that the length of a numeric field was the number of digits before the decimal. It was just recently I found out it was the length including the decimals.

Irritating warnings during check-in

The following RDML code generates 4 warnings:

Delete From_File(KMXDAT) Where('(#SASNR *EQ #WSASNR) *AND (#OWNER *EQ #WOWNER) *AND (#CHAIN *EQ #WCHAIN)')
From Host FFC: Func KMOX / Cmd DELETE / Seq 1082 ....
** Field SASNR was appended, or needs appending, to list of fields because of use in WHERE parameter
** Field OWNER was appended, or needs appending, to list of fields because of use in WHERE parameter
** Field CHAIN was appended, or needs appending, to list of fields because of use in WHERE parameter

However, if I have a logical view with those 3 fields as keys and use them, I get no warnings:

Delete From_File(KMXDAT) With_Key(#WSASNR #WOWNER #WCHAIN)

I understand why I get those warnings but as it is not only illogical but also impossible to add those 3 fields to a delete, they are irritating.

VLF & switching

I have found it extremely useful to use the switching in the VLF to “jump” from one part of the application to another.

Unfortunately, the documentation is not entirely clear on these matters

It works simply by calling avSwitch on the framework manager:

#avFrameworkManager.avSwitch To(BUSINESSOBJECT) Named(BUDGET) Caller(#COM_OWNER) Execute(E6AA038E80AE4EE380BC1B36E753277B)

To() is either to BUSINESSOBJECT, APPLICATION or FRAMEWORK. I have only used it to switch to business objects.

Named() is the name of the business object (application or framework).

vlf1

I have no idea why I need to use Caller(), nor what options I have here.

Execute() is the name of the command I want executed in the business objects. It is the name from the command definition, not from the business object command name.

vlf2

I have also found it useful to pass a value through the manager, using the avSaveValue method. In the command handler signalling the switch I have written

Invoke Method(#avFrameworkManager.avSaveValue) Instance(1) Withid1(WFANE) Fromavalue(#FPLVALG)
#avFrameworkManager.avSwitch To(BUSINESSOBJECT) Named(BUDGET) Caller(#COM_OWNER) Execute(E6AA038E80AE4EE380BC1B36E753277B)

and in the command handler that is then executed I read (and immediately clear) the saved value

Invoke Method(#avFrameworkManager.avRestoreValue) Instance(1) Withid1(WFANE) Toavalue(#FANE)
Invoke Method(#avFrameworkManager.avSaveValue) Instance(1) Withid1(WFANE) Fromavalue('')

It is funny how the Instance, Withid1, Withid2 and Withid3 can be used to store a value in four dimensions. When I was first introduced to the Save/Restore methods I was told that Instance and Withid1 both had to be unique, but the idea is clearly to think of the instance value to enumerate a set of values while Withid1-3 are a, possibly multidimensional, key for saving and restoring the values.

WAM requests and the same field appearing in more than one list

For example, these lists are used in a WAM – there is a reason and that is not important here:

Def_List Name(#FAKLST) Fields(#FAKNUM #BOGTXT) Type(*WORKING) Entrys(*MAX)
Def_List Name(#FAKLST2) Fields(#FAKNUM #FAKKEY) Type(*WORKING) Entrys(*MAX)
Def_List Name(#FAKLST3) Fields(#FAKNUM #WCHAR160) Type(*WORKING) Entrys(*MAX)

In the XSLT the fields are read and inserted:

<xsl:variable name="thelist" select="/lxml:data/lxml:lists/lxml:list[@name='FAKLST']" />
<xsl:variable name="thelist2" select="/lxml:data/lxml:lists/lxml:list[@name='FAKLST2']" />
<xsl:variable name="thelist3" select="/lxml:data/lxml:lists/lxml:list[@name='FAKLST3']" />
<input type="hidden" name="FAKLST.." value="{count($thelist/lxml:list-entries/lxml:entry[1])}" />
<input type="hidden" name="FAKLST2.." value="{count($thelist2/lxml:list-entries/lxml:entry[1])}" />
<input type="hidden" name="FAKLST3.." value="{count($thelist3/lxml:list-entries/lxml:entry[1])}" />
<xsl:for-each select="$thelist/lxml:list-entries/lxml:entry">
  <xsl:variable name="FAKNUM" select="lxml:column[@name='FAKNUM']" />
  <xsl:variable name="BOGTXT" select="lxml:column[@name='BOGTXT']" />
  <xsl:variable name="FAKKEY" select="($thelist2/lxml:list-entries/lxml:entry)[position() = $pos]/lxml:column[@name='FAKKEY']" />
  <xsl:variable name="WCHAR160" select="($thelist3/lxml:list-entries/lxml:entry)[position() = $pos]/lxml:column[@name='WCHAR160']" />
  <input type="hidden" name="{$FAKNUM/@id}" value="{$FAKNUM}" />
  <input type="hidden" name="{$BOGTXT/@id}" value="{$BOGTXT}" />
  <input type="hidden" name="{$FAKKEY/@id}" value="{$FAKKEY}" />
  <input type="hidden" name="{$WCHAR160/@id}" value="{$WCHAR160}" />
</xsl:for-each>

When this form is submitted, #FAKLST gets #FAKNUM and #BOGTXT, but #FAKLST2 and #FAKLST3 only get #FAKKEY and #WCHAR160, respectively. #FAKNUM is only the default value in the #FAKLST2 and #FAKLST3 lists.

No Delete without a Where clause

Delete from file without a where clause causes the RDML function to fail.

This does not work:

Delete From_File(DSTA149)

This does:

Delete From_File(DSTA149) Where('''A'' = ''A''')

No error in compile or check-in.

EDIT:

My misunderstanding. Delete from file without a where clause deletes the previously fetched record. My RDML function failed as I had not fetched anything and I wanted to delete every record.

So

Fetch Fields(#OPDKOD #STATUS) From_File(DSTA149) With_Key(#SEASON #OWNER)
Delete From_File(DSTA149)

would delete one record, the one just fetched.

 

There is a difference between the RDML and RDMLX BIFs

There are some gems on the Lansa forums every once in a while. If you use Lansa (and if not, what are you doing here?) you should register there and follow the threads.

The thread about JSM/JSMX and builtin function signatures is very enlightening and I am quoting it here:

There is a difference between the RDML and RDMLX BIFs.

The RDML BIF cannot access the function internals to determine the working list field definition, so the SERVICE_LIST keyword is required on the command.

The RDMLX BIF can access the working list field definition, so the SERVICE_LIST keyword is not required and ignored.

RDML

1. The following command will only send the command string to the service.

use builtin(JSM_COMMAND) with_args(‘TEST KEY(VALUE)’) to_get(#JSMSTS #JSMMSG)

2. The following command will send the command string and all fields in the function to the service.

use builtin(JSM_COMMAND) with_args(‘TEST KEY(VALUE) SERVICE_EXCHANGE(*FIELD)’) to_get(#JSMSTS #JSMMSG)

3. The following command will send the command string, the working list and all fields in the function to the service.

use builtin(JSM_COMMAND) with_args(‘TEST KEY(VALUE) SERVICE_LIST(*FIELD)’) to_get(#JSMSTS #JSMMSG #WRKLST)

If a working list and the associated SERVICE_LIST keyword is used, then all fields are passed and a SERVICE_EXCHANGE(*FIELD) is not required on the same command.

RDMLX

1. The following command will only send the command string to the service.

use builtin(JSMX_COMMAND) with_args(#JSMXHDLE ‘TEST KEY(VALUE)’) to_get(#JSMXSTS #JSMXMSG)

2. The following command will send the command string and all fields in the function to the service.

use builtin(JSMX_COMMAND) with_args(#JSMXHDLE ‘TEST KEY(VALUE) SERVICE_EXCHANGE(*FIELD|*FIELDS)’) to_get(#JSMXSTS #JSMXMSG)

3. The following command will send the command string and working list to the service.

Note: the function fields are not sent.

use builtin(JSMX_COMMAND) with_args(#JSMXHDLE ‘TEST KEY(VALUE)’) to_get(#JSMXSTS #JSMXMSG #WRKLST)

4. The following command will send the command string, all fields in the function and working list to the service.

use builtin(JSMX_COMMAND) with_args(#JSMXHDLE ‘TEST KEY(VALUE) SERVICE_EXCHANGE(*FIELD|*FIELDS)’) to_get(#JSMXSTS #JSMXMSG #WRKLST)

5. The following command will send the command string and the function fields defined in the field list to the service.

use builtin(JSMX_COMMAND) with_args(#JSMXHDLE ‘TEST KEY(VALUE)’ #FLDLST) to_get(#JSMXSTS #JSMXMSG)

6. The following command will send the command string and function fields defined in the field list and the working list to the service.

use builtin(JSMX_COMMAND) with_args(#JSMXHDLE ‘TEST KEY(VALUE)’ #FLDLST) to_get(#JSMXSTS #JSMXMSG #WRKLST)

(slightly edited by me)

In summary:

  • In JSMX BIFs you can send a working list to the service, with or without sending all or some of the fields in the function.
  • In JSM BIFs you can only choose to send none or all of the fields in the function to the service, and if you send all fields you can also send a working list.

SCANSTRING and lower case searches

This is a case where the documentation is clear but the default value is at best strange.

The built in SCANSTRING function’s 4th parameter, “Compare in uppercase”, which defaults to “1”, meaning no, does exactly the opposite. While the naming of the parameter and the default value is at best strange, the note in the documentation is clear: Using the default value you cannot find anything if searching with a lowercase string.

Not. A. Thing.

Apparently the default value of “don’t compare in uppercase” (if the name is to be taken seriously) means that the string we are searching in is uppercased and then the search is performed case-sensitively.

While if we go for the non-default “compare in uppercase” the string is not changed and if there is a case-sensitive match, we get that result back.

In RDMLX we have a couple of alternatives which seem a lot simpler, all explicitly case sensitive:
STRING.Contains(SEARCHSTRING) (returns true or false)
STRING.PositionOf(SEARCHSTRING) (returns position of match)
SEARCHSTRING.PositionIn(STRING) (returns position of match)

I miss regular expressions.

The difference between Label and Label

Looking for a control to do some specific thing, I landed upon label. Then I started looking for alternatives to do the very specific thing I was looking for and so I found … label. Not the same though.

PRIM_LABL (Label) and PRIM_SLAB (Label) are obviously different. There are a few properties and I believe methods that are missing in one but the documentation does not explain a use case for one or the other, nor why one should choose one (or the other).

I believe the Lansa documentation is severely lacking.

Keyed collections and getting the key

I fear this will be my “Carthago delenda est“: Furthermore, I believe the Lansa documentation is severely lacking.

Case in point, today, using Visual Lansa 14.1 and the online and up-to-date documentation, I wanted to use a keyed collection. I wanted to use this in a way where I can later loop through the collection getting keys and values.

Now, had I started on 7.60.1 FOR Parameters I might have guessed this earlier, but I didn’t. I started on Keyed Collection (PRIM_KCOL). Silly me.

It does tell me how to define a keys collection but in a roundabout way. So the definition goes like this

#PRIM_COL

Like:

Define_Com Class(#PRIM_KCOL<#PRIM_VAR #CHAR512>) Name(#COL1)

which defines a collection #COL1 with keys of type #CHAR512 and values of the variant type, #PRIM_VAR. You have to use repository fields as the key type for some reason, but there is no such requirement for the key value.

Now, #COL1.ItemCount can tell me how many pairs of keys and values are in the collection but there is no way to use this to iterate through the collection.

Since I had not started on the FOR page I tried this:

For Each(#COL1_VAL) In(#COL1)
#COL1_KEY := #COL1.KeyOf<#COL1_VAL>
...
Endfor

which actually works. I did however suspect that it could have some form of performance issues and also would only work consistently if all values were unique.

But wild and semi-random searching of the documentation lead me to the poorly demonstrated Key parameter for the For loop:

For Each(#COL1_VAL) in(#COL1) Key(#COL1_KEY)

This works.

I have also tried to find the documentation on the different assignment operators. I know what +=, -=, *= and /= does, but apparently I don’t know what := does since sometimes I need to use <=:

#VAR_OBJ <= #COL1<#COL1_KEY>
...
#COL1_VAL := #COL1<#COL1_KEY>.String
...
#COL1_VAL := #VAR_OBJ.String

I am guessing <= passes a reference while := assigns a value.

Furthermore, I believe the Lansa documentation is severely lacking.