Sunday, September 13, 2015

Passing parameters to Filemaker Script

Filemaker only accept one parameter when you "perform script". There are many suggestions to pass more than one parameters. Some use custom functions. One of the most noted function is #Parameters. It stores name and value set into a dictionary. Others suggest using "List".

For those who do not use Filemaker advanced, there is no way to create custom functions. For list, if you just pass it a list of values separated by "carriage return". The only problem is that if you passed a blank value, the next value will be used instead. This caused considerable inconveniences.

Some others passes a XML or JSON as parameter and it requires a function to do the extraction. I guess this is the best way to do it. The following is an example of getting the variable value by creating a XML, stored as global value and call the script with node name as parameter.

# Construct a xml for all the variable and values. Store as global variable $$xml
# The passed xml must not contain carriage return just join all the nodes end to end
# The xml constructed must also be escaped to make it valid.
Set Variable [ $pkey ; Value: Get(ScriptParameter) ] 
Set Variable [ $$xml ; Value: Substitute ( $$xml ; "><" ; ">"  & ¦ & "<" ) ] 
Set Variable [ $ccount ; Value: 1 ] 
Set Variable [ $pcount ; Value: PatternCount ( $$xml ; ¦ ) ] 
Set Variable [ $pvalue ; Value: "" ] 
Loop
 Exit Loop If [ $ccount > $pcount ] 
 Set Variable [ $xsearch ; Value: GetValue($$xml;$ccount) ] 
 Show Custom Dialog [ "test" ; $xsearch ] 
 If [ PatternCount ( $xsearch ; "<" & $pkey & ">" ) =1 ] 
  Set Variable [ $pvalue ; Value: Let ( [ $p = Substitute ( $xsearch ; "<" & $pkey & ">" ; "" ) ; $p = Substitute ( $p ; "</" & $pkey &">" ; "" )] ; $p ) ] 
  Set Variable [ $ccount ; Value: $pcount ] 
 End If
 Set Variable [ $ccount ; Value: $ccount+1 ] 
End Loop
Exit Script [ Result: $pvalue ] 

Note that the | symbol is actually the Filemaker "carriage return" sign.





Friday, September 11, 2015

Filemaker example of Xpath

Previously I talked about using Filemaker to extracting xml node text without using plugins. I have created a working script that does just that. You just need to call the first script that does a "Tidy". The script will remove unnecessary carriage return, tabs and extra spaces between nodes. It also add carriage return after every node. You can get the node text value by adding the xpath path with a carriage return in front of the xml as one parameter to the second script.

The reason for putting them in two script is that you only need to "Tidy" the xml once but you need execute  the second script a number of times to extract multiple node values.

The first script is called "xtidy". The script is as follows

Set Variable [ $xml ; Value: Get(ScriptParameter) ] 
Set Variable [ $xlen ; Value: Length ( $xml ) ] 
Set Variable [ $ccount ; Value: 1 ] 
Set Variable [ $xout ; Value: "" ] 
Set Variable [ $xfound ; Value: 0 ] 
Set Variable [ $xtext ; Value: "" ] 
Loop
 Exit Loop If [ $ccount > $xlen ] 
 Set Variable [ $xsearch ; Value: Middle ( $xml ; $ccount ; 1) ] 
 If [ $xsearch = ">" ] 
  Set Variable [ $xfound ; Value: 1 ] 
  Set Variable [ $xout ; Value: $xout & $xsearch ] 
 Else If [ $xsearch = "<" ] 
  Set Variable [ $xtext ; Value: Substitute ( $xtext ; Char ( 13 ) ; "" ) ] 
  Set Variable [ $xtext ; Value: Substitute ( $xtext ; Char ( 11 ) ; "" ) ] 
  Set Variable [ $xtext ; Value: Trim ( $xtext ) ] 
  Set Variable [ $xout ; Value: $xout & $xtext & "<" ] 
  Set Variable [ $xtext ; Value: "" ] 
  Set Variable [ $xfound ; Value: 0 ] 
 Else If [ $xfound = 1 ] 
  Set Variable [ $xtext ; Value: $xtext & $xsearch ] 
 Else
  Set Variable [ $xout ; Value: $xout & $xsearch ] 
 End If
 Set Variable [ $ccount ; Value: $ccount+1 ] 
End Loop
Set Variable [ $xout ; Value: Substitute ( $xout ; "><" ; ">" & ¦ & "<" ) ] 
Exit Script [ Result: $xout ] 
The second script is call "Xpath". The script is as follows



# Just add the path with a carriage return to the xml like $xpath & $xml as parameter
Set Variable [ $xml ; Value: Get(ScriptParameter) ] 
Set Variable [ $xmlcount ; Value: PatternCount ( $xml ; ¦ ) ] 
Set Variable [ $xpath ; Value: GetValue($xml;1) ] 
Set Variable [ $xpath ; Value: Substitute ( $xpath ; "//" ; "" ) ] 
Set Variable [ $xcount ; Value: PatternCount ( $xpath ; "/" )+1 ] 
Set Variable [ $xpath ; Value: Substitute ( $xpath ; "/" ; ¦ ) ] 
Set Variable [ $pathLevelCount ; Value: 0 ] 
Set Variable [ $pathRepeatCount ; Value: 0 ] 
Set Variable [ $lastXsearch ; Value: "" ] 
Set Variable [ $lastXsearchCount ; Value: 0 ] 
Set Variable [ $xmlresult ; Value: "" ] 
Set Variable [ $xcurPath ; Value: "/" ] 
Set Variable [ $xlevel ; Value: 0 ] 
Set Variable [ $xlevelCurrent ; Value: 0 ] 
Set Variable [ $ccount ; Value: 1 ] 
Set Variable [ $xml ; Value: Middle ( $xml ; Position ( Upper ($xml) ; "" ; 1 ; 1 )+15 ; Position ( Upper($xml) ; "" ; 1 ; 1 )-Position ( Upper($xml) ; "" ; 1 ; 1 )-15 ) ] 
Set Variable [ $xmlcount ; Value: PatternCount ( $xml ; ¦ ) ] 
# Script to find lowest level of the first search path
Loop
 Exit Loop If [ $ccount > $xmlcount ] 
 Set Variable [ $xsearch ; Value: GetValue($xml;$ccount) ] 
 If [ Left ( $xsearch ; 2 ) = "</" ] 
  Set Variable [ $xlevelCurrent ; Value: $xlevelCurrent-1 ] 
 Else If [ PatternCount ( $xsearch ; "<" ) = 1 ] 
  Set Variable [ $xlevelCurrent ; Value: $xlevelCurrent+1 ] 
 End If
 Set Variable [ $xnode ; Value: Middle ( $xsearch ; 2 ; Position ( $xsearch ; ">" ; 1 ; 1 )-2) ] 
 If [ PatternCount ($xnode;"/")  ­ 0 ] 
  Set Variable [ $xnode ; Value: ¦ & ¦ & ¦ & ¦ ] 
 End If
 If [ Position ( GetValue($xpath;1) ; $xnode ; 1 ; 1 )  > 0 ] 
  If [ $xlevel = 0 ] 
   Set Variable [ $xlevel ; Value: $xlevelCurrent ] 
  Else If [ $xlevel > $xlevelCurrent ] 
   Set Variable [ $xlevel ; Value: $xlevelCurrent ] 
  End If
 End If
 Set Variable [ $ccount ; Value: $ccount + 1 ] 
End Loop
Set Variable [ $xlevelCurrent ; Value: 0 ] 
Set Variable [ $ccount ; Value: 1 ] 
Loop
 Exit Loop If [ $ccount > $xmlcount ] 
 Set Variable [ $xsearch ; Value: GetValue($xml;$ccount) ] 
 If [ Left ( $xsearch ; 2 ) = "</" ] 
  Set Variable [ $xlevelCurrent ; Value: $xlevelCurrent-1 ] 
 Else If [ PatternCount ( $xsearch ; "<" ) = 1 ] 
  Set Variable [ $xlevelCurrent ; Value: $xlevelCurrent+1 ] 
 End If
 # Look for path start
 Set Variable [ $xpathtxt ; Value: GetValue($xpath;$pathLevelCount+1) ] 
 If [ Position ( $xpathtxt ; "[" ; 1 ; 1 )  ­ 0 ] 
  Set Variable [ $xpathary ; Value: Substitute ( $xpathtxt ; "[" ; ¦ ) ] 
  Set Variable [ $xtxt ; Value: GetValue($xpathary;1) ] 
  Set Variable [ $ycount ; Value: GetAsNumber ( GetValue ( $xpathary ; 2 ) ) ] 
 Else
  Set Variable [ $xtxt ; Value: $xpathtxt ] 
  Set Variable [ $ycount ; Value: 1 ] 
 End If
 # Look for path end
 Exit Loop If [ Position ( $xsearch ; $lastXsearch ; 1 ; 1 ) = 1 ] 
 If [ Position ( $xsearch ; "<" & $xtxt & ">" ; 1 ; 1 ) = 1 and $xlevelCurrent = $xlevel+$pathLevelCoiunt ] 
  Set Variable [ $pathRepeatCount ; Value: $pathRepeatCount+1 ] 
  If [ $pathRepeatCount=$ycount and $pathLevelCount  < $xcount ] 
   Set Variable [ $pathLevelCount ; Value: $pathLevelCount+1 ] 
   Set Variable [ $lastXsearch ; Value: "</" & $xsearch & ">" ] 
  End If
  If [ $pathLevelCount = $xcount and $pathRepeatCount = $ycount ] 
   Set Variable [ $xmlresult ; Value: Middle ( $xsearch ; Position ( $xsearch ; ">" ; 1 ; 1 )+1 ; Position ( $xsearch ; "<" ; 1 ; 2 )-Position ( $xsearch ; ">" ; 1 ; 1 )-1 ) ] 
   Set Variable [ $ccount ; Value: 50 ] 
  End If
  If [ $pathRepeatCount=$ycount and $pathLevelCount < $xcount ] 
   Set Variable [ $pathRepeatCount ; Value: 0 ] 
   Set Variable [ $lastXsearch ; Value: "</" & $xtxt & ">" ] 
  End If
 End If
 Set Variable [ $ccount ; Value: $ccount + 1 ] 
End Loop
Exit Script [ Result: $xmlresult ] 



Wednesday, September 09, 2015

How to read XML nodes if you don't have XML parser?

I used Filemaker to create database applications. On of the application need to access to Web Services. The returned text is XML. Therefore, I use a plugin called BaseElements to read the nodes. However, recent development requires the same functionality to be available in iPad. Filemaker for iPad does not allow plugins.

Fortunately, the xml parser is not a general parser that translates XML to an array. It just returns the text value of the node defined by the path.

To circumvent the issue, I decided to replicate the same functionality of the BaseElement plugin (BE_XPath() function). The function works like this. You pass it a XML and a path and it returns you the result. For exmaple, BE_XPath($xml;"//Data/myname") will return you the text value from the path. If there are more than one "Data" then you need to define it as a sort of array like Data[1].

It is not at all difficult to interpret the xml. There is just one step to do before parsing it. You need to add [Carriage Return] after every node. Usually the xml is returned as a continuous text without a carriage return character. What I need to do is just search for "><" and replace it with ">[CR]<". [CR] is the character code 13.

The result of doing such replacements means each node will be on a line ending with a carriage return. For example,

<myname>xxx</myname><myphone>yyy</myphone>

will be come

<myname>xxx</myname>
<myphone>yyy</myphone>

What you need to do is search for the path in the sequence for each line of the XML. My example uses Data and myname. So I just read each line and look for first. If it is found then I look for . If reaches first then is not found. If the line returns "" before the two nodes are found then the path is not available.

If the path is Data[x], I just need to repeat the search for till the x occurrence is reached. It is as simple as that.

Once the node name is found, it is time to remove the xml node name. Well, it is quite simple. You simply replace the enclosing node names together with its XML markup as "". Don't worry as XML does not allow "<" and ">" to be part of node text.

Obviously, you need to look out for <myname/>. This means that the node exists but no value. But since I am looking for the node text, whether it exists or have no value is not an issue, I just return a blank unless it is specifically required to determine whether the node actually exists.

The above is assuming that the XML is without carriage returns, tabs and spaces. If the xml is generated by hand and is "tidied". the situation more complicated. You will need to reformat the xml to remove all the carriage return, linefeed, and leading and trailing spaces first then proceed to add carriage return as per described above.




What is the difference between SOAP and POX on Web Services

My previous two blog talks about two aspects of Web Services. What is the differences between the two methods. To make it simple, it is basically the same except that one is easier and the other is harder. They both return exactly the same result.

Let's talk about the easier one. Using SOAP is by far the easiest way. You just need to compose an array according to the documentation available for the Web Services and send to a function of the WS object.

The harder one is to compose the XML envelop then add the nodes to make a complete XML document and then send it.

How did the Web Services work with two different methods? Actually they are both the same except that the XML is posted to the second stage. Allow me to explain the process.

When we use SOAP, we creates a connection to the Web Services (henceforth WS). We then called the particular function via the connection object and pass it an array of parameters. In PHP the script is as follows.

$client= new SoapClient("http://webservices_address", $opt);
$param=array('action'=>'theaction');
$test=$client->thefunction($param);
echo $test->functionresponse->result;

Now if the arguments is a multiple array type the $param will be something like this

$param=array('action'=>array('subaction'=>subtheaction'))

You have to know what function to call and what parameter to pass. Normally this is found in WS documentations. Now, it is very important to be specific about the object name of the array. It is often case sensitive. "subaction" is not the same as "subAction". Often WS will simply tells you that a parameter is missing because of case differences.

To a user, the PHP script is all that is needed to get it working. In actual fact PHP works in the back ground and actually posts the data in a different form. You guess it right. It actually posts a XML to WS after composing it from the WSDL that it first addressed to. This means that it actually interprets the returned value when it first posts to WS, compose the XML based on the definition then sends the XML to another address (usually a sub address of the original) according to the definition.

Our second method actually composes the XML directly and posts to the sub address. It is therefore the same thing except you need to compose the XML the hard way unless you download the WSDL of the WS and do a transpose yourself.

How does the multiple array looks in XML? It is exactly the same except in different format.

subtheaction

Obviously, the PHP array is much easier to read. Now, you would say why not just use SOAP since it is much easier? Yes it is unless the application you use does not have SOAP functionality. PHP have both SOAP and CURL (which provide POX functionality by sending the XML as post). If I use PHP I would prefer to use SOAP instead of CURL. What if you use an application for your business but that application only have CURL?

Since CURL can post a XML to the WS and still gets back the same result, it is pretty alright to use it to access WS. It will be more difficult as you will need to understand what to do when composing the entire XML. Some WS does provide the XML information and allows POX.

Filemaker is a good application for a small business. It is very easy to create a complete database application without much technical knowledge. To access to WS, there is no choice but to use CURL. It might be harder but it will still work.

Actually Filemaker can post to a web site and gets the result but then it has to call a PHP script, pass the information to it and it in turn calls WS, get the result and return it to Filemaker. It is pretty round the world way but it still works.