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.

 

comment posted Brendan, USA

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.

Getting Started

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");
}

Differences from C++

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.

Types

foal has the following types:

  • int a 32-bit integer
  • bool a boolean value, false=0, true=1
  • num a precision decimal number
  • str a Unicode string
  • CMarkup a markup document
  • foal 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.

    Strings

    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.

    Numbers

    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

    Lists and Arrays

    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.

    Structures

    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).

     

    comment posted 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

    Split XML with XML editor script

    Split XML file into smaller pieces