The firstobject access language "foal" is the fast and powerful programming language in the free firstobject XML editor (foxe) you can use to perform operations on XML and other markup documents. With foal you can retrieve information, merge, split, and transform documents, and compute reports. Built around the CMarkup API with a C++ like syntax, C++ and foal code samples can be used with little change in either context. With foxe's built in debugging aids you can rapidly develop and test CMarkup solutions.
I've used and loved your XML editor for a couple of years now. Just this week I discovered its scripting language and now I love it even more. |
When you create a program document in foxe via File -> New Program, or right-click Generate Program, you get a program document that is mostly the same as a data document except that it has right-click options for Compile F7, Run F9, Run to Cursor Ctrl+F9, and Debug Step F10. Any document you save or load with a .foal extension is automatically considered a foal program.
The program document contains functions. If there are multiple functions, the unit will determine the function that runs (F9) by looking for a function named main()
, or else the last function that has either no parameters or a single CMarkup argument (in which case foxe lets you select an open document as input). Here is an example program with 2 functions (get_name
and main
):
get_name(CMarkup &m, str strId) { str strSearch = "\\name[@id='" + strId + "']"; return m.FindGetData(strSearch); } main() { CMarkup m; m.Load("C:\\Test.xml"); return get_name(m,"456A"); }
foal is based on C++ syntax, just like Java is. This is not to be confused with being C++. foal supports a small subset of C++ syntax and capabilities as well as some of its own unique flavors. The C++ syntax was chosen to be compatible with the examples already supplied in the firstobject documentation.
The main differences from C++ are the fundamental types str
and num
and the automatic conversion of types with operators or when they are passed to functions.
foal has no pointers. Functions can be declared to accept arguments "by reference" meaning that operations on the variable inside the function will affect the variable that was passed in. Plus foal allows default values for reference arguments, unlike C++.
Also, for a function that does not return a value, the void
keyword in front of the function declaration is optional (and you cannot use void
in the parameter list).
foal currently only supports while
loops. Any loop can be written as a while
loop, so this keeps the syntax simple. People not accustomed to C/C++ languages may be unfamiliar with the parts of a for
loop or the syntax of a do
loop, so it is debatable whether these should be supported in foal.
foal has the following types:
int
a 32-bit integerbool
a boolean value, false=0
, true=1
num
a precision decimal numberstr
a Unicode stringCMarkup
a markup documentfoal provides automatic conversion between types for assignments and passing to arguments as follows:
Converted To: | |||||
---|---|---|---|---|---|
bool |
int |
num |
str |
CMarkup |
|
bool |
0 or 1 |
0 or 1 |
"0" or "1" |
error | |
int |
true if != 0 |
exact | minus, digits | error | |
num |
true if != 0 |
lose fraction | "." separator | error | |
str |
error | minus, digits | "." separator | SetDoc |
|
CMarkup |
error | error | error | GetDoc |
When going between numbers and strings, a period is always used to represent the decimal point "separator". This ensures that programs work the same way regardless of locale. It is also good practice to standardize on a period in XML documents that may pass between locales, and just use the implicit conversion that expects periods. When dealing with locale dependent display or data entry, use the NumToFormat
function to create a locale-specific number string for displaying, and NumFromFormat
to convert from an input string.
Most examples in the CMarkup documentation can be run in the firstobject access language, with only minor changes relating to the string type. Since CMarkup is developed primarily to work with both MFC (CString
) and STL (std::string
) strings, simple modifications are necessary when using an example written for one or the other. This means changing the string type name and converting any string functions to the foal string functions such as StrLength
, StrCompare
, StrFind
, StrMid
.
The str
type is Unicode, and you cannot assume an correspondence between length and number of characters unless you know your text is ASCII.
An int
is like a num
where the number of decimal places is always zero.
The num
type supports decimals like 9.95
. It is not implemented as a C-style floating point number, but instead as an integer with a "floating" number of decimal places. In other words, 9.95
is stored as 995
, and number of decimal places of 2
. This allows foal to support more accurate arithmetic than C-style floating point number types (float
and double
) which have inherent inaccuracies (see Precision and Accuracy in Floating-Point Calculations).
Because num
retains a number of decimal places, specifying 9.50
is the same value as 9.5
but you are telling it to maintain two decimal places rather than one. foal will keep the number of decimal places as additions and subtractions are made.
9.50 + .5 = 10.00 9.50 - 9.5 = 0.00
With division and multiplication, the number of decimal places may grow if required to up to the maximum precision supported by the type. With multiplication it will never be more than the total of the decimal places in the operands. With division it can often go to the limits of precision and you should probably round the result to the desired precision.
9.50 * 2.0 = 19.00 .3 * .5 = 1.5 2.01 * 0.3 = 0.603 .3 / 2 = 0.15 .30 / 2 = 0.15 20 / 3 = 6.66666666
You can confidently compare num
values, and int
values too, regardless of the number of decimal places:
1.5000 == 1.5 1 == 1.0
Also, there are no floating-point concerns with rounding, it always yields the expected results:
NumRound( 9.5 ) == 10 NumRound( 9.85, 1 ) == 9.9 NumRound( 9.95, 1 ) == 10.0 NumRound( 155, -1 ) == 160
Here is some code to create a list of values:
CMarkup mList; mList.AddElem( "E", "Smith" ); mList.AddElem( "E", "Doe" ); mList.AddElem( "E", "Jones" );
<E>Smith</E>
<E>Doe</E>
<E>Jones</E>
You can loop through all the items in the list with FindElem
. To iterate in reverse order starting at the last sibling just replace FindElem
with FindPrevElem
in the following example.
mList.ResetPos(); while ( mList.FindElem() ) { str sLastName = mList.GetData(); // ... }
Although it is more efficently used as a list where you loop through the elements sequentially, you can also access it like an array. To go directly to the second item and get the value call:
str sVal = mList.FindGetData("/*[2]");
Note the slash in /*[2]
which means absolute path so that it does not matter where the current position is when you call this. Using the asterisk instead of the tag name E means that the tag name does not matter.
There are no structures or classes like C++, but you can store any complex set of data in a CMarkup object (see Dynamic Structure Documents). The subdocument functions (AddSubDoc, GetSubDoc) can help mimic substructures as well (see Subdocuments and Fragments of XML Documents).
FOAL C++ short circuit evaluation
Joe McKeown 01-May-2009
Something I noticed (the hard way) is that FOAL doesn't seem to support 'short circuit evaluation,' at least not in my usage in an 'if' test scenario. Not a big deal to work around it, just a difference from C++ that you might mention in the existing documentation about the differences between FOAL and C++.
Good point. I intend to implement this because it is an assumption people make when using C++ syntax that the second part of a conditional expression will not get called or evaluated when the first part makes evaluating the second part unecessary. If the first argument of a logical OR evaluates to true or if the first argument of a logical AND evaluates to false, then it doesn't need to evaluate the second argument. This makes the code for the short circuit operators (||
and &&
) act like a control structure. In the following example, the expectation would be that FindElem(sName)
will only be called if sName
is not an empty string:
if ( sName != "" && m.FindElem(sName) ) ...
Although the firstobject XML editor foal evaluates this conditional statement correctly, it does not currently support the expected "short circuit" when sName
is empty. It will call the FindElem
method and possibly change the current position within the CMarkup object even if sName
is empty. For now, you have to work around it with something like this:
if ( sName != "" ) { if ( m.FindElem(sName) ) ... }
I will update here when the expected usage is implemented.
See also:
Using the firstobject XML editor from the command line
Counting XML tag names and values with foal
Format XML, indent align beautify clean up XML
Brendan, USA