Tutorial - Action Statements and Method in Candle


One innovative feature of Candle is that it better integrates functional programming with procedural programming than any other language does.

Candle enforces a mechanism called separation-of-side-effects. That is in Candle, procedural code can call function code, but not vice versa. And expressions in Candle are designed to be always functional. By enforcing such restrictions, your functional code is more clearly separated from procedural code. You should put as much of your program logic in functional code as possible. In this your program will be more robust and maintainable. If you have not been convinced of the benefits of functional programming, you should really google and read for yourself.

In current beta release, due to security reason, the online query editor only allows functions, thus for examples in this tutorial, you'll have to download Candle and try it on your local machine. We'll try to address that in the next beta release.

Procedural Statements

In the preceding tutorial, we have already covered procedural flow control statements in Candle. They are: if statement, for statement, foreach statement, let statement, with statement, switch statement, put statement, set statement, while statement, continue statement, break statement, return statement.

Node construction statements, like apply and apply-to statements, are not directly allowed in method body.

Besides these statements, the final group of statements in Candle are node update statements. As they all have side-effects, they are only allowed in methods.

Node Update Statements

Statement Type Syntax Remarks
insert statement insert src-node-expr into trg-node-expr;
src-node-expr before trg-node-expr;
src-node-expr after trg-node-expr;
Copied the source node from one location to another. insert into statement copies the source node as the last child node of target node. insert before statement copies the source node before the target node. And insert after statement copies the source node after the target node. Both src-node-expr and trg-node-expr should evaluate to a single node. (We'll support a sequence of source nodes in next beta release). The source node can be in different document from the target node.
set attribute statement set path/@attr = value; The last step of the path must be a qualified name preceded by attribute axis. If the attribute with the qualified name does no exist, it is created with the given value; otherwise, the attribute node is updated to the given value.
move statement move src-node-expr into trg-node-expr;
src-node-expr before trg-node-expr;
move src
-node-expr after trg-node-expr;
Similar to insert statement. But instead of copying, the source node is moved to new location.
delete statement delete node-expr; The node is deleted.
rename statement rename node-expr as expr; If the node is an attribute or element, it is renamed to the specified new name.

Node Output Statements

The input('uri') function loads a document into the memory. The node update statements update the nodes of the documents in the memory. To serialize the changes back to the physical storage, you'll need to use node output statements:
Statement Type Syntax Remarks
save statement save node-expr; The save statement serializes the entire document containing the node-expr back to its original location in its original format.
update statement output node-expr at uri-expr as format; The update statement serializes only the node-expr to the location specified by uri-expr.  The as-format clause is optional. If it is omitted, it is default to be Candle Markup. The format can be one of the predefined value:
  • "cmk": for Candle markup;
  • "text": for plaint text;
  • "xml", for XML;
  • "html", for HTML;
  • xhtml", for XHTML;

Method Definition and Invocation

The syntax to define a method is as following:

  method name(param as type, ...) { statements; }
method name(param as type, ...) as type { statements; }

The return-value clause of a method is optional. If it is not specified, then method does not return anything, and return statement cannot appear in the body of this method.

Due to separation-of-side-effects, you cannot call method directly in an expression. Then how can one check the return value of a method? you may ask. Well, Candle provides a special built-in function, called result(), to retrieve the return value of the last invoked method. In other words, the return value of a method call is stored in a special slot specific to the current execution thread. result() function just returns the result stored in this slot. A new method call with return value replaces the value in this slot.

Besides method call, node insert and move statements also changes the value in this slot. The value in the slot is changed to point to the node just inserted or moved.

To some extend, the procedural programming in Candle is quite like PL/SQL programming. The difference is that Candle's underlying data model is hierarchical, whereas PL/SQL's underlying data model is relational.

It's time to put what we've learned into practice. The example below inputs an XML document containing CD records, sorts them and output to a new document:
method cd-sorted(cd-doc) as element {
      foreach (cd-doc/catalog/cd) order by title?string { copy .; }
method main() {
  output(result(), 'new-cd.xml', candle:core:xml;