Libretto 0.9 Tutorial

Abstract

Libretto 0.9 is a query language for data and knowledge management on the level of object descriptions:

Table of Contents

1. A Query Example in Libretto
2. Ontologies and Prefixes
3. Multiple Inheritance
4. Objects and Their Modifiers
5. Queries to an Ontobase
6. Predicates
7. Sequences
8. Modifiers, Predicates and Sequences
9. Data Types
    9.1. Datatype v:string
    9.2. Datatypes v:int and v:long
    9.3. Datatype v:float
    9.4. Datatype v:date
    9.5. Datatypes v:dmapString and v:dmapBinary
10. Asterisks
11. Variables
12. Indices and Operation at
13. The Dot
14. Operation as
16. Collection-wise and Element-wise Expressions
19. Anonymous Objects
20. Temporary Objects
21. Maps
22. The Types of Objects: A Summary
23. Expressions if
24. Functions
25. Built-in Functions
26. The Quote (Expressions as Data)
27. Indexing
28. Sorting
29. Annotations
30. Question Marks
31. Libretto Expressions
32. An Ontobase Management Demo
33. Input/Output
34. XML and HTML Documents
35. Embedded Languages

A Glossary

1. A Query Example in Libretto

The basic construct of Libretto is a query to an ontobase. Here is a simple example of a query:

    Person[name == "John"]/child[gender=="male"];
This query collects all the sons of persons named John. It has the form of a path consisting of two steps, which separated by a slash. The path is evaluated from left to right:
  1. All persons known to the ontobase are collected (the objects of the class 'Person').
  2. Among them, Johns are selected (by predicate [name = "John"]).
  3. Each John's child is picked up (a transition through the property 'child').
  4. Finally, among Johns' children the boys are selected (by predicate [gender="male"]).

Queries are evaluated in a context, a sequence of elements serving as input parameters. The context is either taken from 'outside' (from the external environment), or formed at the first step of the query. In our example the context is created at the first step. This is a sequence of all known persons selected by 'Person'.

Queries not only retrieve data from the ontobase. They can modify it. Suppose we want to add the comment to each John's son that he is a John's son. For this we augment the query with a modifier. It massively adds a new property to each of the sons:

    Person[name == "John"]/child[gender=="male"] {comment = "A John's son"};

The context for the modifier {comment = "A John's son"} is the sequence of all Johns' sons obtained at the previous steps. This modifier assigns the string "A John's son" to the property 'comment' of each context element. Modifiers are always indicated by curly brackets {...}.

Generally, a Libretto query has the form of a path, that is a sequence of steps separated by slashes:

transition1 [ predicate1 ] { modifier1 } / ... / transition-n [ predicate-n ] { modirier-n };

A query is terminated by a semicolon. The basic components of steps are:

a transition
makes a transition from a context sequence to a new one. In the example above: child makes the transition from persons named "John" to their children.
a predicate (optional)
lets through only those elements of the context sequence, which satisfy the predicate's condition. In the example: the predicate [name = "John"] allows only the persons named "John".
a modifier (optional)
modifies the inner state of the context elements. In the example: the modifier assigns the string "A John's son" to the property 'comment' of all known John's children.

Steps can also generate new entities in the ontobase. To illustrate this and other components of Libretto let us describe in Libretto a jukebox. Each song in a jukebox has musical aspects (e.g. a song's genre), as well as commercial aspects (e.g. playback prices).




2. Ontologies and Prefixes

We start with the introduction of a new 'jukebox' ontology:

        ontology jb "http://music.ontobox.org/jukebox";

This is done by the Libretto's operation ontology, which takes as parameters the prefix (jb) and the unified resource identifier (URI, "http://music.ontobox.org/jukebox") of a new ontology. Ontologies: The method of placing a knowledge in an ontology is simple and elegant: data published in the ontology is marked by its URI ("http://music.ontobox.org/jukebox" for the ontology above).

URIs are good to preserve the global uniqueness of ontologies on the Web. On the other hand, URIs are cumbersome and heavy, so in Libretto we use instead their shorthands called prefixes. The prefix jb is introduced above for the 'jukebox' ontology. If we mark a name with the prefix of a certain ontology we mean that this name denotes an entity belonging to this ontology. For instance, if an entity named foo belongs to the ontology with prefix jb, we write jb:foo. Some ontology in the ontobase can be declared as default:

        prefix . "http://music.ontobox.org/jukebox";

The dot instead of a prefix means that if a name is not marked by any prefix, then the entity it denotes belongs to the default ontology "http://music.ontobox.org/jukebox". Now, after the application of two operations 'ontology' and 'prefix', both styles of names (prefixed by jb, and without prefixes) can be applied to 'jukebox' entities.

The difference between operations ontology and prefix becomes apparent when an ontology with the indicated URI does not exist in the ontobase. In this case ontology creates a new ontology and assigns it the URI, whereas prefix can change prefixes of only existing ontologies and, thus, generates a runtime error if the ontology does not exist.

Instead of the complete and rigorous definition of an ontology, we can use its simplified form - ontology;. It declares a default ontology with URI "http://temp.ontobox.org/" augmented by the time of its creation, e.g. http://temp.ontobox.org/2010-03-05/16-53. Such a shortcut is convenient in small programs working locally, in which a heavy and explicit ontology declaration is unnecessary. The date/time substring makes this name unique.

Since all the principal concepts of the 'jukebox' ontology have titles (songs, genres, collections of songs), we start with the introduction of a general class of titled objects:

        class jb:Titled {
                jb:title v:string};

The parameters of this class are determined by the modifier. Modifiers in Libretto are always enclosed in curly brackets {...}. The prefix jb means that the class Titled is defined in the 'jukebox' ontology. If to use the ontology's URI instead of the prefix, we get the URI (full name) of this class

        http://music.ontobox.org/jukebox#Titled

Naming in Libretto is based on the URI specification, but with some restrictions. For instance, the minus symbol while being a correct short name in URI (e.g. "http://foo#-"), is interpreted in Libretto as the subtraction operation. Nevertheless, Libretto allows arbitrary short names admissible in URI. If a name has a prefix then all that follows the colon is interpreted as a name, e.g.  ont:-. And if such a name belongs to the default ontology, it must start with the colon, like in :-. Another syntactic trap is that a-1 is interpreted as a name. If we want to subtract 1 from a then the minus sign must be framed by blank symbols: a - 1.

Names defined in an ontology must be unique within it. In particular this means that since jb:title is defined as a t-property, it can not be declared again within the same ontology. This approach differs from that in many object oriented languages, in which the scope of a property name is a class definition.

Since we have declared the 'jukebox' ontology as default, the prefix in the definition of Titled can be omitted:

        class Titled {
                title v:string};

The properties, which link objects with datatype attributes (like a 'title' string or an 'age' integer) are called t-properties (datatype properties). The declaration

        title v:string

introduces a t-property title. The property is declared as a v:string property, and this means that its values must be strings. Datatypes are predefined in a special Libretto ontology with the prefix v (see 9 Data Types).

Note that we use the keyword class to specify class definitions. Let us introduce the main classes of the 'jukebox' ontology:

        class Genre extends Titled {                   # the class of genres  inherits from Titled
            subgenre Genre            # o-property subgenre introduces
        };                              # genre hierarchies

        class Song extends Titled{                    # the class of songs inherits from Titled
                artist v:string,    # artist's name
                genre Genre};         # song's genre

        class JukeboxItem {                  # an item stored in the jukebox
                price v:int,            # the price of the item
                jbcode v:int};          # ID of the item in the jukebox

The classes Genre and Song inherit from the class Titled. In particular this means that both of them inherit from it the t-property title. Syntactically to establish inheritance we place the superclass name in the class modifier. Comments in Libretto start with '#' and stop at the end of the line.

Together with t-properties the definitions above contain o-properties (object properties). The values of o-properties are objects. For instance, the o-property genre (defined in Song) determines the song's genre: it links the song with an object of the class Genre.

Libretto follows the open world assamption: it is assumed that in general the ontobase contains only incomplete data about the domain of discourse.

The ontobase can be gradually augmented with new data and knowledge. In particular such gradualness works for class definitions. E.g. we can define the class Song step by step, starting with the fact that such a class exists:
	class Song;
Now our ontobase contains this class, and is ready to operate with it. For instance, it can create a new object of Song. But if we want to say anything specific about this object, it is necessary to augment the information about the class. In particular, we can determine it as a subclass of Titled, and then add to it the property artist:
	class Song extends Titled;
	class Song {artist v:string};
and so on.

3. Multiple Inheritance

Libretto supports the multiple inheritance of classes. This means that a class can inherit from several superclasses simultaneously. For instance,
        class JukeboxSong extends Song, JukeboxItem;

Here JukeboxSong is a class of songs stored in a jukebox. JukeboxSong inherits from both Song and JukeboxItem classes. This means that the nature of objects belonging to this class is twofold. They are both: art pieces and commercial products.

4. Objects and Their Modifiers

Objects are the main inhabitants of ontobases. Libretto provides many possibilities for object handling. We distinguish two types of actions changing ontobase content:

Generators create new entities, whereas modifiers change their inner state. For instance, a generator can explicitly introduce an object in the ontobase. The names of objects start with the ampersand '&'. A generator begins with the name of a class, to which the new object must belong. The generator

        Genre &Rock;

creates in the ontobase an object, which belongs to the class Genre and is identified as &Rock. This object is still 'blank': we know nothing about it except that it belongs to Genre. To define the inner state of the object, modifiers are needed.

Syntactically modifiers are indicated by the curly brackets {...}. We used modifiers in the class definitions above. Modifiers can occur at any step of queries, when it is necessary to change the inner state of the context elements. In particular, using modifiers we can define the title of the object &Rock and introduce a couple of new genres:

        Genre &Rock {           
                title = "Rock"};
        
        Genre &Rockability {
                title = "Rockability"};

        Genre &RockAndRoll {
                title = "Rock and Roll"
                subgenre = &Rockability};

In the last query Rockability is defined as a sub-genre of Rock'n'Roll. Note that in the first query the generator Genre &Rock does nothing, because &Rock is already known to the ontobase as the object of the class Genre. Therefore the structure of the first query is superfluous, though correct, and can be replaced by

        &Rock {           
            title = "Rock"};	

In this query &Rock is used for the selection of the existing object by its name.

Let us augment the hierarchy of genres by introducing the genre Hard Rock and adding sub-genres to Rock:

        Genre &HardRock {
                title = "HardRock"};

        &Rock {
                subgenre = &RockAndRoll,
                subgenre .= &HardRock};

The object &Rock obtains two values of the subgenre. In general, a property can have an arbitrary number of values. Note that we use here two different operators '=' and '.='. The difference between them is that '=' erases previous values of the property before adding new ones, while '.=' adds new values at the beginning of the sequence of existing values.

The values of properties in objects are always ordered.

It was stressed above that information can be added to the ontobase gradually. For instance, the values of the property subgenre can be added to the object &Rock in two steps:

    &Rock {
            subgenre = &RockAndRoll};
    &Rock {
            subgenre .= &HardRock};

The following operators are used in modifiers:

        = - destructive assignment
        += - adding after existing values
        .= - adding before existing values
        -- - value delete

The operators are applied in the order they occur in the modifier. If we asked the following query:

        Genre {
                title .= "Before",       
                title += "After",
                title = "Single",
                title --
                };

the ontobase would first select all existing genres as the context sequence. Then each genre from the context would obtain the new title "Before". Then the title "After" would be added after "Before". Then all previous values would be removed and substituted by "Single". Finally, the last operator removes the remaining value of the t-property title.

We are ready to introduce a couple of songs:

        JukeboxSong &HeyJude {
                artist = "The Beatles",
                title  = "Hey Jude",
                price  = 20,
                jbcode = 223,
                genre  = &Rock}; 

        JukeboxSong &RockClock {
                artist = "Bill Haley & His Comets",
                title  = "Rock Around the Clock",
                price  = 12,
                jbcode = 10,
                genre  = &Rockability};
It is important that the multiple inheritance is implemented in Libretto not only on the level of classes (see the definition of JukeboxSong), but also on the level of objects. For instance, we can write:
        JukeboxItem Song &RockClock1;
Or objects can be added to classes step by step:
        JukeboxItem &RockClock2;
        Song &RockClock2;
In the modifier of the second query Song is interpreted as an operator adding the object to the class Song.

Now let us introduce a separate ontology for describing the folklore music:

        ontology f "http://music.ontobox.org/folklore"; 

        Genre &f:Skiffle {
                title = "Skiffle"};

        JukeboxSong &f:ChewingGum {
                artist = "Lonnie Donegan",
                title  = "Does Your Chewing Gum Lose It's Flavor",
                jbcode = 320,
                genre  = &f:Skiffle};

Note that the object &f:Skiffle, belonging to the ontology "http://music.ontobox.org/folklore", is defined in the class Genre belonging to the other ontology, "http://music.ontobox.org/jukebox". Its properties artist, title, jbcode and genre also belong to the default ontology.

Information about entities in the ontobase can be distributed over several ontologies.

Before we continue with the jukebox, let us consider another example in order to demonstrate the capabilities of object modifiers without spoiling the 'jukebox' ontology. We introduce a simple ontology about persons:

        ontology . "http://persons.ontobox.org/";

        class Person {
                name v:string, 
                surname v:string,
                fullname v:string};

        Person &john {name = "John", surname = "Peters"};
        Person &peter {name = "Peter", surname = "Johnes"};

When a modifier changes an object it has access to the current object's values of properties. This opportunity makes Libretto a very flexible and expressive instrument for ontobase updates. In particular, the full names of all people from the class Person can be generated by a single modifying query

        Person {fullname = "{name} {surname}"};

The string

        "{name} {surname}"
is a parametric string (see 9 Data Types) depending on the values of name and surname in the current context object. Now, let us ask the ontobase about the full names of John and Peter:

        &john/fullname;          # = ("John Peters")
        &peter/fullname;         # = ("Peter Johnes")

The results are established in the comments. We see that the single query above have generated the values of fullname for both objects.

For the third type of modifications - removing entities from the ontobase - special built-in functions are used, like v:delete() and v:force-delete().

In the recent queries we worked in a new ontology named "http://persons.ontobox.org/", which was temporarily set as default. Now it is time to get back to the 'jukebox' ontology:

        prefix p .;
        prefix . jb;
First we define a new prefix p for the 'person' ontology, and then set the 'jukebox' ontology as default. Note that prefixes and the dot are used here for renaming. Let's check:
        p:Person;  # = (&p:john, &p:peter)

5. Queries to an Ontobase

The structure of the 'jukebox' ontobase, as it has been developed so far, is established below. Essentially the ontobase consists of the two ontologies:

Libretto provides flexible means for querying ontobases. We start with the collection of class elements:

        Genre;  # = (&HardRock, &Rock, &Rockability, &RockAndRoll, &f:Skiffle)
        Song;   # = (&HeyJude, &RockClock, &f:ChewingGum)

Queries are terminated by the semicolon ’;’. The results are shown in the comments.

Slashes ’/’ are used to construct a path of a step sequence. Along this path, figuratively speaking, Libretto should move in the ontobase in order to obtain the desired results:

        &RockClock/title;       # = ("Rock Around the Clock")
        Genre/title;            # = ("HardRock", "Rock", "Rockability", "Rock and Roll", "Skiffle")
        &HeyJude/genre/title;   # = ("Rock")

The value of the whole query is equal to the value of its rightmost step (title in the queries above).

Intuitively, a path acts as a 'conveyor system' or a 'production line', in which every step obtains as input some values (the input context), and outputs other values (the output context). The output context plays the role of the input context for the next step (if that exists), or the path result (otherwise).

For instance, the evaluation of the last query above can be illustrated by the following scheme:

In Libretto we can define inverse o-properties, denoted by the operation '~':

        &HeyJude/genre;                 # = (&Rock)
        &Rock/~genre;                   # = (&HeyJude)
        &Rock/~genre/genre;             # = (&Rock)
The inverse property ~prop for an o-property prop is defined as follows: if an object &B is among the values of the property prop of an object &A (that is, &B belongs to the result of the query &A/prop), then &A is among the values of the property ~prop of &B.

6. Predicates

A predicate plays in a query the role of a filter letting through only those values of the input context, which satisfy the conditions of the predicate:

        Genre[subgenre];                # = (&Rock, &RockAndRoll)
        Song[jbcode == 320];             # = (&f:ChewingGum)
        Song[jbcode == 320] / genre;     # = (&f:Skiffle)
        Song[jbcode == 320] / price;     # = ()

The empty sequence () plays in predicates the role of false. For instance, the first query above collects those genres, which have a non-empty sequence of sub-genres. In predicates we can use the ordinary logical connectives:

        Genre[not subgenre];    # = (&HardRock, &f:Skiffle, &Rockability)
        Song[price > 12 and genre/subgenre];    # = (&HeyJude)

The first query generates a sequence of 'leafs' in the hierarchy of genres (that is, genres, which do not have sub-genres). The second query can be explained as follows: find songs, the price of which greater than 12, and the genre of which has sub-genres. One more example of this kind:

        Genre[~genre/jbcode == 320];     # = (&f:Skiffle)

Here we use the inverse property of genre, in order to find a genre, a song of which has code 320 in the jukebox. Note that the class Genre contains the object f:Skiffle stored in a different ontology. The following queries illustrate links between paths and predicates:

        Genre/subgenre/subgenre;        # = (&Rockability)
        Genre/subgenre[subgenre];       # = (&RockAndRoll)
        Genre[subgenre/subgenre];       # = (&Rock)

All these paths look for 3-step chains in the hierarchy of genres. We have only one such chain: Rock – Rock’n’Roll – Rockability. Since the value of a query always equals the value of its rightmost step, the first query returns the 'grandchild' Rockability, the second - the 'father' Rock’n’Roll, and the third returns the 'grandparent' Rock.

Any expression of Libretto can play the role of a predicate.

7. Sequences

The sequences of objects and data values play the key role in Libretto. Sequences are used to represent

Syntactically sequences are indicated by parentheses (...). In Libretto there are two basic types of sequences: the sequences of objects and the sequences of data values. Mixing objects and data values within one sequence is not allowed:
            (&HardRock, "HardRock")

Sequences in Libretto are flat, that is, a sequence can not be an element of another sequence:

        ((&HardRock, &Rock), &Rockability)  =  (&HardRock, &Rock, &Rockability)

Sequences can serve as queries:

        (Song[jbcode > 200], Genre[not subgenre]);
        
                # = (&HeyJude, &f:ChewingGum, &HardRock, &Rockability, &f:Skiffle)

This query merges in one sequence the results of two separate sub-queries.

Sequences are also useful for simultaneous assignment of several values to an object's property. As an example, let us introduce several prices for Hey Jude and Rock Around the Clock:

        &HeyJude {
                price = (30, 35, 40)};        # = (&HeyJude)
                
        &RockClock {
                price += (50, 70)};            # = (&RockClock)
                
        &HeyJude/price;         # = (30, 35, 40)
        &RockClock/price;       # = (12, 50, 70)

Note that in Hey Jude the old price was substituted by the new ones, whereas in the second song the new values were added to the existing price.

In the conclusion of this section let us consider another example, in which sequences act as path's steps:

        &HeyJude/(price, 1)/(. + 5);    # = (35, 40, 45, 6)   
The sequence (price, 1) at the second step has the object &HeyJude as its input context. Thus its evaluation gives ((30, 35, 40), 1). And since all sequences in Libretto are flat, we obtain (30, 35, 40, 1). At the third step the singleton (. + 5) has the elements of (30, 35, 40, 1) as its input context. The dot . here is a 0-ary function, which returns the value of the current context element (see 13 The Dot). The evaluation of the singleton for each of these values gives:
        ((30+5), (35+5), (40+5), (1+5)) = ((35), (40), (45), (6)) = (35, 40, 45, 6)

8. Modifiers, Predicates and Sequences

Let us gather within one table the facts about the basic Libretto structures, which

typesyntaxroleexample
modifier{...}modifies the elements of the input context&HeyJude {price .= 1}
predicate[...]filters the elements of input contexts&HeyJude [price > 1]
sequence(...)generates a new sequence basing on the elements of the input context as parameters &HeyJude / (price, 1)

The next table classifies the operators acting in modifiers:

typesyntaxroleexample
class operator<class name>adds a context element to a class&HeyJude {Song}
property operator<path><operator><term>modernizes the values of an object's property&HeyJude {price .= 1}
variable operator<variable><operator><term>assigns the variable a new value in the context of the modified object &HeyJude {X = price}

Variable operators will be considered in the following sections. The next example illustrates the features of the first two operators. Let us generate a new song &HelloDolly, and through its modifier publish in the ontobase data relevant to this new object. In the modifier we demonstrate the behavior of the operators, sequences and predicates:

        Song &HelloDolly {              # new song is generated
            genre = Genre &Musical,    # new genre is generated and assigned
            genre/title = "Musical",       # genre's title is defined
            JukeboxItem,                    # &HelloDolly is added to JukeboxItem
            price = (10, 20),              # multiple values are assigned to price   
            price [. < 15] += (30, 40)};    # the sequence of price values is modified
This query generates a new song &HelloDolly. In the first operator of the modifier a new genre &Musical is created and immediately assigned to the corresponding property of the song. Note that the property operator genre/title = "Musical" defines the title of not the song, but the new genre. The next class operator adds the new song to the class JukeboxItem (this is an example of multiple inheritance on the level of objects), in order to include data about the prices in the song object. Then price is assigned the sequence of values (10, 20). The final operator can be explained as follows: among the price values find values smaller than 15, and insert after each of them two new values 30 and 40. The appropriate values are collected by the predicate [. < 15]. We have only one such a value. This is 10, and hence new values will be inserted only once:
        &HelloDolly/price;    # = (10, 30, 40, 20)
Let us verify the correctness of the data:
        &HelloDolly/genre/title;    # = ("Musical")

9. Data Types

In the previous sections we actively used data types, such as strings and integers. Data types determine the range of t-properties. In general, the datatype system of Libretto corresponds to the specification XML Schema. The datatypes are described in a predifined Libretto ontology with the standard URI http://www.w3.org/2001/XMLSchema and the prefix v. The basic types of Libretto are v:string, v:int, v:long, v:float, v:date.

9.1. Datatype v:string

Strings in Libretto have two syntactically equivalent representations - with single and double quotes:

        "This is a string" 'This is a string' "This is 'a string'" 'This is "a string"'

The empty string is equivalently denoted by "" and ''. Escape sequences allow for the representation of some non-graphic and system characters:

\n

line feed

\r

return

\t

horizontal tab

\\

backslash

\"

double quote

\'

single quote

\b

backspace

\f

form feed

\{

opening curly bracket

\uXXXX

unicode character, e.g. \u754C

\xXX

hexadecimal byte, e.g. \x8A

\OOO

octal byte, e.g. \237

In Libretto a string can not occupy several lines. But to have a possibility to represent multiline texts as strings, several strings running one after another are considered as a single string:

        "this "  "is "  "a string"     equals     "this is a string"

Parametric strings. Libretto allows for inserting evaluated expressions into strings, and this is a compact and flexible way to manage strings. The insertions are made within the curly brackets. For instance,

         Song/"{title} has price {price}"!; 
		# Hey Jude has price 303740 
		# () has price 10304020
		# Does Your Chewing Gum Lose It's Flavor has price () 
		# Rock Around the Clock has price 125070 
		# Smoke on the Water has price ()

The simbol "!" is needed to evaluate the expression inside curly brackets. A string is generated not only if all expressions inserted in it are not equal to the empty sequence (that is, false). In the example above the strings are created for those songs, the price or title of which are known. Simbols of empty brackets are inserted instead of unknown property.

a string corresponding to this song is not created. The same problem is for &HelloDolly: its title is not determined. In case when evaluation of an expression returns a sequence of several values, they are represented as a sequence in parentheses (the results for price in the example above). If it is necessary to generate a string for each value separately, we can do this with the operation as.

9.2. Datatypes v:int and v:long

Ordinary integers are used in Libretto, e.g.

        123  -20  1 73850

The standard arithmetic operations are defined on integers:

        +  -  *  div (деление нацело)  mod (остаток от деления нацело)

Let us reiterate once again:

Since the minus symbol "-" in accordance with the specification URI can occur in names, and "*" is not only the multiplication sign in Libretto, in order to avoid errors, please surround these symbols with the blank character.

We can compute arithmetic expressions in the paths:

        Song/(price * 3); # =  (36, 150, 210, 30, 90, 120, 60, 90, 105, 120)

This query multiplies by 3 all the prices of all the songs stored in the ontobase. Singleton parentheses are necessary to take into account the operation priorities (see 31 Libretto Expressions): the query Song/price * 3 is interpreted as (Song/price) * 3. Note that this query also returns the correct result:

        Song/price * 3; # =  (36, 150, 210, 30, 90, 120, 60, 90, 105, 120)

This happens because during the computation the interpreter makes the exhaustive search of all available values of the arguments.

9.3. Datatype v:float

In Libretto ordinary floating point reals are incorporated. The standard arithmetic operations are applied to them.

        123.5     -10.2       116.1 + 15.3        10.1 div 4.2

9.4. Datatype v:date

Libretto provides means for date/time handling. The following string formats are recognic as dates and times:

yyyy-MM-dd HH:mm:ss.SSSZ

yyyy/MM/dd HH:mm:ss.SSSZ

yyyy-MM-dd'T'HH:mm:ss.SSSZ

yyyy/MM/dd'T'HH:mm:ss.SSSZ

yyyy-MM-dd HH:mm:ss.SSS

yyyy/MM/dd HH:mm:ss.SSS

yyyy-MM-dd'T'HH:mm:ss.SSS

yyyy/MM/dd'T'HH:mm:ss.SSS

yyyy-MM-dd HH:mm:ss

yyyy/MM/dd HH:mm:ss

yyyy-MM-dd'T'HH:mm:ss

yyyy/MM/dd'T'HH:mm:ss

yyyy-MM-dd HH:mm

yyyy/MM/dd HH:mm

yyyy-MM-dd'T'HH:mm

yyyy/MM/dd'T'HH:mm

yyyy-MM-dd

yyyy/MM/dd

We distinguish absolute and relative date/time values. Absolute dates are string values with the letter 'Z' or a time zone of the form [+|-]HHmm as a suffix. For instance,

        "2009-09-15T03:21:34.567Z"      "2008-12-20 10:00+0200"   

'Z' denotes Coordinated Universal Time (UTC) zone (which in practice coincides with Greenwich Mean Time, GMT). The suffix [+|-]HHmm informs about the time shift from UTC. In particular, "+0200" means that this date/time is represented in Central European Time (CET).

Relative dates/times do not contain explicit data about their time zone, e.g.

        "2008-12-20"          "2009-10-25T01:12:34.567"

To interpret relative dates/times Libretto uses the concept of a current time zone. UTC (that is, shift +0000) is a predefined current time zone. It can be modified by special built-in functions. Note that if your application does not need information about absolute time (which is calculated as the number of milliseconds since 1970-01-01T00:00Z), and does not jump from one time zone to another, you can ignore the time zone information. Let us demonstrate how we could use dates in the 'jukebox' ontobase. Suppose we need to record the publishing dates for songs in the jukebox. For this, we add a new t-property in the class JukeboxItem

        class JukeboxItem {
                pdate v:date}

Let's record the date of publishing the Beatles' Hey Jude in the jukebox:

        &HeyJude {
                pdate = "2008-12-20 10:00+0200"};

This means that we have 'loaded' it in the jukebox on December, 20, 2008 at 10:00 CET. Now,

	&HeyJude/pdate; # = ("2008-12-20T08:00:00.000Z")

Note that Libretto have made the transition from CET to UTC. This is done, because OntoBox stores time in the absolute form corresponding to UTC. If the current time zone is different from UTC, necessary recalculations can be easily done by special built-in functions.

9.5. Datatypes v:dmapString and v:dmapBinary

Libretto heavily uses the memory for data processing. So, in order to reduce the memory usage while handling the large amounts of data (book texts, song tracks, images, etc.), Libretto can be explicitly instructed that certain data should be loaded from disks (or similar storage devices) only if it is directly necessary. For this Libretto supports two special datatypes v:dmapString and v:dmapBinary.

The datatype v:dmapString is used to externally store textual data. Logically v:dmapString is not distinguishable from v:string.

The datatype v:dmapString does not have any special data safety behavior. For instance, in the basic OntoBox implementation the values of both types v:dmapString and v:string are stored in the ontobase in a permanent non-volatile way.

Note that v:dmapString is defined in the box ontology (since the XMLSchema v ontology can contain only standard datatypes). Now, we can introduce the class of books in the 'person' ontology:

	class p:Book {
		author p:Person;             
		book-text v:dmapString;         
	}
Here we have a new t-property book-text, in which long texts can be saved.

Note that v:dmapString is useful only if its values are handled in a 'passive' way. If they are involved in active processing, the difference between v:string and v:dmapString disappears.

Libretto also supports binary data, which is represented by the datatype v:dmapBinary. For instance, we can add a person's image to the class Person:

        class p:Person {p:image v:dmapBinary};

An explicit inclusion of binary data in a Libretto code is syntactically impossible. But Libretto supports two well-known string formats for representing binaries: Base64 and Hex. For illustration let us consider a tiny green-pixel-gif-image example. The size of this picture is 43 bytes. In the Base64 format (where each byte of binary data is denoted by a two-byte hexadecimal number) it is represented by the string

	"R0lGODlhAQABAIAAAAB9QwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw=="
In the Hex we have:
	"47494638396101000100800000007d4300000021f90400000000002c00000000010001000002024401003b"
The Base64 format is default: if to write
        &p:john {p:image = "R0lGODlhAQABAIAAAAB9QwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw=="};
the string value is interpreted as a Base64 binary datum. If we want to convert it from the Hex format, a special built-in function must be applied
        &p:john {p:image = 
            ("47494638396101000100800000007d4300000021f90400000000002c00000000010001000002024401003b")
                /v:hex-to-bin()};
Now,
        &p:john/p:image        
            # = ("R0lGODlhAQABAIAAAAB9QwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==")
        &p:john/p:image/v:bin-to-hex();    
            # = ("47494638396101000100800000007d4300000021f90400000000002c00000000010001000002024401003b")
The use of quoted expressions allows us to handle disk-map data without its loading in the memory. For instance, binary data can be directly transferred from file to file:
        &p:john/v:bin-set(%p:image, 0, "john.jpg");  # assigns to the zero element of p:image
        &p:john/v:bin-add(%p:image, -1, "john1.jpg"); # inserts after the last element of p:image
        &p:john/v:bin-export(%p:image, "john-new.jpg", 1); # exports from the 1st element of p:image
The similar functions v:text-set/3, v:text-add/3 and v:text-export/3 are used for the values of v:dmapString.

10. Asterisks

A variety of 'asterisk' expressions is used for the selection of all objects of certain collections. This group consists of the expressions of three types:

        *;      # = (&RockClock, &Rock, &Rockability, &HelloDolly, &RockAndRoll, 
                       # &Musical, &HardRock, &HeyJude)

        f:*;     # = (&f:ChewingGum, &f:Skiffle)

        *:*;     # = (&f:ChewingGum, &f:Skiffle, &RockClock, &Rock, &Rockability, &HelloDolly, 
                       # &RockAndRoll, &Musical, &HardRock, &HeyJude) 

The first query picks up all objects of the default ontology, the second one collects all objects of the 'folklore' ontology prefixed f, and the third query collects all objects of all ontologies.

These constructions are easily combined in queries with other steps. In particular, if we want to gather all songs from the 'folklore' ontology, we should ask:

       Song/f:*; # = (&f:ChewingGum)

One more example:

        *:*/genre;       # = (&f:Skiffle, &Rockability, &Musical, &Rock)

This query collects all existing values of the o-property genre (actually they occur only in the objects of the class Song). *:* as the first step can be omitted:

        /genre;         # = (&f:Skiffle, &Rockability, &Musical, &Rock)

Asterisk steps are good for generating starting context sequences in queries. It was stressed above that the query context must be defined, otherwise the result will be empty as in the following example:

        genre;          # = ()

11. Variables

Variables are used in Libretto for keeping the general computation environment, as well as in some other situations. Variable definition starts with keyword "var". For instance,

        var X = &Rock/subgenre;
        X/title;               # = ("HardRock", "Rock and Roll")

In this example the variable X is assigned the evaluation result of the path &Rock/subgenre. Now the variable can be used in queries and other assignments:

	X = X/subgenre;
	X/title;				# = ("Rockability")

In Libretto contains also special variables, which play specific roles, Their name start with the underscore '_'. An example of usage of this variable can be found in the next section.

12. Indices and Operation at

Libretto allows the selection of elements in sequences by their indices (like it can be done in arrays). For this, a special infix operation at is used. Some examples:
	("a", "b", "c", "d") at 2;            # = ("c")
	("a", "b", "c", "d") at (0, 3, 5);    # = ("a", "d")
Element enumeration starts with 0. The second example demonstrates the possibility of the selection of several elements by their indices. Here elements with indices 0 and 3 are collected. The 5th element does not exist in the sequence.

Indices can be also operated within predicates. For this, a special variable $_i is used, which is equal to the index of the current element in the context sequence. For instance, the following query is equivalent to the last query above:

	("a", "b", "c", "d") [$_i = (0, 3, 5)];    # = ("a", "d")

13. The Dot

The general rule states that the dot '.' is equal to the current context value. The dot is an analogue of "this" (or "self") in object oriented programming languages. E.g., the query with the dot

                *:*/jbcode[. > 200];     # = (223, 320)

looks for those values of the t-property jbcode, which are greater than 200.

Predicates and other expressions are evaluated in Libretto in a element-wise way: the computation for each element of the context sequence runs separately. In the following example
        (9, 0, 8, 1, 7, 2, 6, 3, 5, 4)[. > 5];          # = (9, 8, 7, 6)
first 9 > 5 is verified, then 0 > 5 etc., and only if the current value satisfies the condition, it moves to the output context sequence.
The following query collects the elements equal to their own index in the sequence:
	(0, 2, 10, 5, 4, 100) [. = $_i];    # = (0, 4)

It is convenient to use the dot in modifiers. The following modifier doubles the Hey Jude's price:

         Genre[title=="Rock"]/~genre {price at 1 = . + 2};            # = (&HeyJude)

The modifier starts with the path Genre[title="Rock"]/~genre, which collects the sequence of objects for modification (in this case, it is (&HeyJude)). The dot in the right part is used for the excess to the old value(s) of the modified property price. The operation at selects the value with necessary index: e.g. price at 1 selects the second value of price in the current object. As the result of this modification we get

                &HeyJude/price;         # = (30, 37, 40)

14. Operation as

The dot provides the access to the current value obtained at the previous evaluation step, but can not provide access to results of the earlier steps of evaluation. In order to save data, which appear along the query path, we can use variables with a new operator as. A simple example:

	("a", "b") as x / (1, 2) / "{x}:{.}"!;  # = ("a:1", "a:2", "b:1", "b:2")
The result of this query uses both: the value obtained at the first step of the path (via the variable x), and the value obtained at the second step (via the dot '.'). The order of the resulting strings in the output sequence explains how the Libretto interpreter acts. In this example we have two choice points. At the first step it is necessary to select one of the values "a" and "b", and at the second step - the one of 1 and 2. This means that the result must contain 2×2 = 4 elements. The structure of the output sequence shows that the Libretto interpreter follows the depth-first strategy. Initially, at the first step the value "a" is picked up, then at the second step 1 is chosen. After that the first resulting element "a:1" is generated. Then Libretto returns to the nearest choice point, selects 2 and generates the next value - "a:2". Since all alternatives of the second choice point are exhausted, the interpreter returns to the first choice point and reiterates the procedure with "b".

Variables together with the operator as provide a very flexible and compact data collection technique. In particular, using as we can organize iteration along the query path. For instance, if we need to collect songs together with their genres, we can write:

	Song as sng / genre / "The genre of {sng / title} is {title}"!;
        
                # = ("The genre of Does Your Chewing Gum Lose It’s Flavor is Skiffle",
                        # "The genre of Hey Jude is Rock",
                        # "The genre of Rock Around the Clock is Rockability")

A current song is saved in the variable sng, and retrieved when necessary. &HelloDolly is not present again, because its property title is not defined.

16. Collection-wise and Element-wise Expressions

Note that for the evaluation of the double dot .. we need to collect all possible values of the context sequence. An expression, the evaluation of which needs the knowledge of the whole context sequence, is called collection-wise (otherwise, it is element-wise). Let us check the next query:
         ("a", "b") as x [. == "a"] / x;   # = ("a")
This query is evaluated for the elements of the sequence ("a", "b") separately. This element by element evaluation is equivalent to the separate evaluation of two queries:
        1. ("a") as x [. == "a"] / x;   # = ("a") 
        2. ("b") as x [. == "a"] / x;   # = ()
The situation is different when you need all elements of ("a", "b") as a collection. Using as operator you can get this behaviour:
        1. ("a", "b") as x   # = ("a", "b")
        2. ("a", "b") [("a", "b") == "a"]    # = ("a", "b")
        3. ("a", "b") / x     # = "b"   
The value of x appears to equal "b" (that is, the last element of the sequence), because all the values are assigned to x at the moment of constructing the sequence for the predicate [.. == "a"]. Hence, collection-wise expressions change the scenario of query evaluation, and this must be taken into account, especially if variables are involved in the query. The recipe here is simple: do not include collection-wise steps between the variable assignment and its use.

19. Anonymous Objects

The names (identifiers) in Libretto plays a role, which resembles the role of foreign keys in databases. Their basic task is to distinguish objects from each other and provide access to them. We can generate new objects as anonymous, in order not to waste time for name invention. In anonymous objects the name is substituted by an underscore. This is an example of anonymous object generatition in the 'person' ontology from section 4:
        prefix . p;        # temporarily declare the 'person' ontology as default
        Person &_ {name = "Tom"};     # = (&_1265457818855-2)	
The system assigns an anonymous object a 'surrogate' name (&_1265457818855-2 in this case), which will be used for references. Anonymous objects are very convenient for massive object generation. Suppose, we need to add in the ontobase the information about three persons, and we know only their names - John, Richard and Paul. We could introduce the objects for each man explicitly:
        Person &john {name = "John"};
        Person &richard {name = "Richard"};
        Person &paul  {name = "Paul"};
But this way is non-elegant and hardly scalable. Using anonymous objects we can solve the problem in a more compact way:
        ("John", "Richard", "Paul") as nm / Person &_ {name = nm}; 

                # = (&_1266817260780-0, &_1266817260780-1, &_1266817260781-2) 
Here the object generator Person &_ {name = nm} uses the context information stored in the variable nm. For each context value a new anonymous object is generated. Note that in this example the object generator plays the role of a query step.

If it is necessary to get access to anonymous objects, it can be done via the values of their properties, e.g.

        Person[name = "Richard"] {name += "Dic"};   # = (&1266817260780-1)
        Person[name = "Richard"]/name;              # = ("Richard", "Dic")

20. Temporary Objects

Different object can be of different importance. There can be 'secondary' objects, the lifeline of which fully depends on other, 'primary' objects. Temporary objects are also useful for storing intermediate data, returning results etc.

Suppose that while working with the 'person' ontology (section 4), we need to create a phone book with personal contact information like phone numbers, addresses etc. Since the information about a person can be very diverse and complicated, it is inconvenient to collect all these data within one class. In particular, it looks reasonable to separate the contact information in a special class, with adding a new 'contacts' property to the class Person:

        class Contacts {
                tel v:string;
                e-mail v:string};
                
        class Person {
            contacts Contacts
        };

Let us introduce Tim:

        Person &tim;

Now we can create separately a 'contacts' object for Tim. The name of this object defined explicitly allows us to operate with it:

	Contacts &tim-contacts {
                tel = "332233444"};
        
        &tim {
                contacts = &tim-contacts};    
        
        &tim/contacts/tel;             # = ("332233444")

Evidently, this way is heavy and inflexible. One more problem appears when we have to remove Tim from our phone book. To us the object &tim-contacts is secondary and valuable only together with its owner - the object &tim. For the ontobase both objects are of equal importance and independent, though linked through the property contacts. So, if to remove the object &tim from the ontobase, we get in it a 'hanging' object &tim-contacts. This is a 'cascade data' problem, which could be solved if &tim-contacts played a secondary role with respect to &tim and depended on it.

The situation described above is similar to another problem. During a computation we usually need to store somewhere temporary data, e.g. to keep intermediate results. The structure of objects is quite convenient for this. But the creation of an ordinary object means that it is stored in the ontobase permanently. So, we have to apply a garbage collection and other cleaning techniques.

All these problems can be solved by the introduction of temporary objects. For instance, using temporary objects we can define contacts as follows:

        Person &tim {contacts = Contacts &&_ {e-mail = "a@b.c"}};

The double ampersand && declares that the object with Tim's contacts is temporary. In particular, if the object &tim has been removed, then the garbage collector removes the temporary object, since no references left from permanent objects to it. Though it is not obligatory, the object containing contacts is defined as anonymous, because it can be accessed via the main object &tim:

        &tim / contacts / e-mail;          # = ("a@b.c") 
Thus, the life cycle of a temporary object depends on permanent objects. If there are no links to the temporary object from the permanent ones (e.g. these objects or links were deleted), then the temporary object is erased by the garbage collector. If a temporary object is generated in a Libretto program for auxiliary purposes, it is removed after the finish of the program.

Actually temporary objects are elements of a special class Temporary defined in the OntoBox system ontology http://ontobox.org/. Thus,

        Person &&Tim;

and

        Person v:Temporary &Tim;

are equivalent generators (box is the predefined prefix of the system ontology). An object is temporary, while it is an element of the class v:Temporary.

21. Maps

A map is an object with a simplified management of t- and o-properties. The properties of ordinary objects are introduced in the class definitions(for instance, name is a property of Person). You do not need to do it in maps. Necessary properties can be introduced directly within the map definitions. Such properties are called map-properties or keys. This makes maps very flexible (at the price of weaker typing). Maps in Libretto are analogues of hash maps in programming languages. Syntactically, maps start with an ampersand followed by a modifier in curly brackets:

	&{key1 :.+= value1, ..., key-n :.+= value-n}

Here :.+= denotes one of the following operators =, += or .=.

The only naming restriction for map keys is that the same name can not be used simultaneously as an o- and a t-property. In particular, the following definition is erroneous:

          &{tmp = &ivan, tmp = 1},
since the property tmp is simultaneously assigned a data value and an object.

Maps are elements of a predefined class Map, which is introduced in a special Libretto ontology

        http://ontobox.org/map
with the predefined prefix map. Thus, a map of the form &{...} can be alternatively defined by
        map:Map &_{...}

The basic rules of map handling:

  1. Excluding specific features of keys, maps are ordinary objects of the ontobase;

  2. Map keys differ from ordinary properties by the lightweight creation and handling style. In particular, they should not be declared in advance in class definitions, they are created together with the map itself;

  3. The type of a key (whether it is a t- or an o-property) is determined automatically based on the type of the value assigned to this key.

Maps are convenient for a quick packing of incoherent data, e.g. if it is necessary to collect in the ontobase several interdependent values. For instance, if we need to collect songs together with their genres, we can ask:

	Genre as g/&_{                       
		@t-exist = g/title; 
		@p-exist = g/~genre/price     
	};
                                                                
                        # = (&_1266823727186-5, &_1266823727188-6, &_1266823727207-9)

While answering this query, Libretto creates three maps in the default ontology and returns their automatically generated names. Now we can work with these maps. The following query picks up all objects with non-empty values of the properties @t-exist and @p-exist (these are exactly our new objects), and retrieves from them the data about genres:

	map:Map[@t-exist and @p-exist] / @t-exist;   # = ("Rockability", "Rock", "Musical")

Note that only three maps were generated, though we have six genres in our ontobase. The idea is that

maps are created only if all keys defined by the operation '=' are assigned non-empty values.

Since only for three genres both @p-exist and @t-exist are assigned non-empty values, the number of the output maps is 3.

If a key is optional and allowed to be empty, then '+=' or '.=' are used instead of '='.

For instance, if to ask

        Genre/&{
                @t-exist = title
                @p-all += ~genre/price};   
        
                        # = (&_1266823727219-11, &_1266823727221-12, &_1266823727222-13,  
                                &_1266823727223-14, &_1266823727225-15, &_1266823727229-16)

then we get a map for each genre. And the number of the maps with non-empty p-all is again 3:

        map:Map[@t-exist and @p-all]/@t-exist;             # = ("Rockability", "Musical", "Rock")

The map creation technique, which combines '=' with '.=' and +=, provides opportunities for generation of only relevant and necessary objects.

Any object can be defined as a map. For this, we just declare that this objects is of the class map:Map. For instance, we can introduce a map object for a new genre

        Genre &tmpgenre {
                map:Map;
                @tmp-comment = "Temporary genre"}

Multiple inheritance cures some problems of map typelessness, when the same class map:Map is used for representation of data of very different kinds. Let us reconstruct the 'person' ontology in a cascade style by combining the advantages of different types of objects:

	class Person;

	class Contacts {
	        tel v:string;
	        e-mail v:string};

	class PersonalData {
	        spouse Person;
	        birthdate v:date};

	class Passport {
	        id v:string;
	        issued v:date};

	class Person {
	        name v:string;
	        contacts Contacts;
	        data PersonalData;
	        passport Passport};

	Person &jane;

	Person &john {
	        name = "John";

	        contacts = Contacts &&{
	                tel = "332233444";
	                e-mail = ("john@aa.b", "manager@aa.b");
	                secret-box = "in an old house"};

	        data = PersonalData &&{
	                spouse = &jane
	                birthdate = "1970-10-10"};

	        passport = Passport &&{
	                id = "12345678 A"
	                issued = "1999-11-10"};
	};

Double ampersands indicate that the maps are defined as temporary objects. Anonymous objects of classes Contacts, PersonalData, Passport are created together with the permanent object &john. The use of maps instead of ordinary anonymous objects makes it possible to keep in the object some extra data. In particular, personally for John a unique communication method via a secret box (the property secret-box absent in the definition of Contacts). This cascade structure resembles the approaches to object generation in other languages like JSON and JavaFX. Its features significantly strengthen the expressive power of Libretto. The query examples:

        &john/contacts/tel;             # = ("332233444")
        &john/data/birthdate;           # = ("1970-10-10T00:00:00.000Z")
        Person[contacts/secret-box]/name;   # = ("John")
In the last query we find out with whom we can contact throwgh a secret box. This example shows that the style of handling map keys is the same as we use for ordinary properties.

22. The Types of Objects: A Summary

Now let us combine in a table the facts about different types of objects:

objectspersistenttemporary
ordinary&john {name="John"}&&john {name="John"}
anonymous&_ {name="John"}&&_ {name="John"}
maps&{name="John", tmp=1}&&{name="John", tmp=1}

23. Expression if

A conditional expression allows us to branch the query evaluation depending on the value of the condition. For instance,

        if (Genre) "yes" else "no";             # = ("yes")

means that the class of genres if non-empty. if is a key word of Libretto, whereas 'else' is not. One more example in the 'jukebox' ontology:

        Song/(if (price) "{title} has a price"! else "{title} has no price"!);
        
                # () has a price 
				# Hey Jude has a price 
				# Rock Around the Clock has a price 
				# Does Your Chewing Gum Lose It's Flavor has no price 
				# Smoke on the Water has no price

Conditional expressions can occur in parametric strings, and this helps us to make the last query more compact:

        Song/"{title} has {if (price) "a" else "no"} price";

More examples of conditional expressions can be found in section 24 Functions.

Another well-known construct is a loop (iterator), which repeatedly applies a sub-query to each element of a sequence. The procedural semantics of queries is iterative itself, and the usage of the operator as makes for-expressions superfluous. For instance, the loop can be expressed in a folowing compact way:

        ("a", "b", "c")/"{.} - {.}"!;                    # = ("a - a", "b - b", "c - c")

If we need to iterate over several sequences simultaneously, the loop is defined like the following one:

        ("a", "b") as i / (1, 2) as j / "{i}{j}"!;

One more example from the 'jukebox' ontology: with as:

        &HeyJude as song / price / "{song/title}'s possible price is {.}"!;
        
                # = ("Hey Jude's possible price is 30", 
                        # "Hey Jude's possible price is 37", 
                        # "Hey Jude's possible price is 40")

24. Functions

The 'jukebox' ontology contains the hierarchy of musical genres. For instance, since Rock Around the Clock belongs to the genre Rockability, and since Rockability is a sub-genre of Rock (via Rock’n’Roll), this song evidently belongs to the genre Rock. But if we ask,

	&RockClock[genre == &Rock];      # = ()

then get the empty sequence, that is, the negative answer because the jukebox description does not contain explicit data about this fact. Libretto does not have tools to work with transitive closures, which could be helpful here, but allows the definition of new functions. In fact, Libretto includes a simple but elegant functional language, which is good for solving many tasks, and makes Libretto more flexible. Let us start with the classical example, the definition of factorial:

    def fact(N){ 
        if (N == 0) 1 
            else N * fact(N - 1);
		}

Here N is a local variable (global variables are forbidden within function definitions). In Libretto functions with the same name but different numbers of arguments are different functions. Therefore, to indicate a function we should mention both, its name and arity, e.g. fact/1. The next one-step query computes the factorial of 5:

	fact(5);        # = (120)

Using the dot we can reorganize the definition of the factorial in such a way that it takes as its argument the context values:

        
    def fact(){
        if (. = 0) 1 
            else . * (. - 1)/fact();
        }

Now:

	(1,2,3,4,5)/fact();     # = (1, 2, 6, 24, 120)

Note that the function fact/0 works separately with each element of the context sequence. In Libretto a function has to options for its behavior w.r.t. the context sequence. A function can be applied to each element of the context separately, and a function can be apply once to the whole context sequence. In first case we have the element-wise behavior, an in second case, the behavior is collection-wise (see 16 Collection-wise and Element-wise Expressions). Thus a function in Libretto can be

Collection-wise functions are marked by the word gen. The double dot is valid for both types of functions, but a single dot works only with element-wise functions, because separate context values do not exist. Let us demonstrate the difference in two behavior types. The function fact/1 is defined as element-wise, so it is applied to each value of the context:

	(1,2,3,4,5)/fact(5);    # = (120, 120, 120, 120, 120)

Since the definition of fact/1 does not depend on the context values, all five times we obtain the same value. But we can do the following trick:

	(1,2,3,4,5)/fact(.);    # = (1, 2, 6, 24, 120)

Let us define a collection-wise version of the factorial Fact/1:

    gen Fact(N) {
        if (N = 0) 1 
            else N * Fact(N - 1);
        }

Fact/1 is evaluated only once, independently on the number of elements in the context sequence:

	(1,2,3,4,5)/Fact(5);    # = (120)

Though element-wise and collection-wise functions act differently, syntactically their names are not distinguishable. But in order to make Libretto programs more readable and manageable we recommend to follow the agreement:

The names of element-wise functions start with lowercase letters, and the names of collection-wise functions start with uppercase letters.

The next function checkGenre/1 solves the problem we talked above:

        &RockClock/checkGenre(&Rock);         # = (&RockClock)
Acting as a filter, this function should return only those context songs, which belong to the genre in the argument (directly or not). Here is the definition:

	def checkGenre(Gnr) {              # checks subgenres recursively
		var SubGenre = Gnr/subgenre;
	
		if ( Gnr/title == ./genre/title ) .
			else if ( SubGenre ) checkGenre( SubGenre )
				else ()
	};

The basic facts linked to this definition:

Now,

        Song/checkGenre(&Rock);         # = (&RockClock, &HeyJude)

This query collects not only &HeyJude, which explicitly belongs to the genre &Rock, but also &RockClock, the genre of which is explicitly defined as &Rockability.

25. Built-in Functions

The interpreter of Libretto provides more than 150 built-in functions - they are describe here. The names of built-in functions are defined in a special ontology of Libretto named http://ontobox.org/libretto/builtins. The predefined prefix of this ontology is fn. Some examples:

        Genre[title/v:contains("ff")];             # = (f:Skiffle)
        &HeyJude/v:classes();               # = ("http://music.ontobox.org/jukebox#JukeboxSong")
        ("abc", "def")/v:string-codes();        # = (97, 98, 99, 100, 101, 102)
        ("HardRock", "Rock", "Skiffle")/v:compare("Rock");   # = (-10, 0, 1)
        ("a", "b", "c")/v:count();                           # = (3)
        ("*** aa")/v:match("^\\*\\*\\*\\s+(.*)", "")/v:group(1); # = ("aa")

The only function in these examples, which starts with an uppercase letter is v:Count/0. We remind that in accordance with the agreement, the names of collection-wise functions start with uppercase letters. For instance, v:Count/0 calculates the number of elements in the context sequence. It is obvious that in general till the whole context sequence is collected, it is impossible to evaluate the number of its elements. This is why v:Count/0 is collection-wise.

We can combine built-in and user-defined functions:

        def p(){ 
            v:match("(\\d).*", "")/v:group(1);
        }  
        ("abc", "1")/p();       # = ("1")       
The interpreter of Libretto has a flexible programming interface for adding new built-in functions. A Java programmer can do this by oneself.

26. The Quote (Expressions as Data)

The back quote % converts computable queries into passive data by preventing them from being evaluated. This construct is standard for functional programming (e.g., quote in Lisp). Compare the following queries in Libretto:
        Song;   # = (&HeyJude, &RockClock, &f:ChewingGum)
        %Song;  # = (org.ontobox.libretto.adapter.ClassId@4)

        (1,2,3,4,5)/fact(.);     # = (1, 2, 6, 24, 120)
        _{(1,2,3,4,5)/fact(.);}    # = {(1, 2, 3, 4, 5) / fact(.)}
In second and fourth cases quoted expressions are considered as data. We can combine them in sequences with other data values:
        (%Song, 1 + 2);  # = (%Song, 3)

Syntactically % is a prefix operation with high priority. In the last example the scope of % is restricted by the comma. If we want to restrict the quote scope by one query step, it is necessary to use parentheses:

        (%Genre)/v:name();    # = ("Genre")

The veto upon evaluation is removed by built-in function v:eval/1:

        var x = _{1+2};    # = {1 + 2}
        x;            # = (%1+2)
        v:eval(x);   # = (3)
        (1,2,3,4,5) at v:eval(x);    # = (4)

For those, who can use it, quoting provides significant benefits by introducing in Libretto higher order function definitions. For instance, we can define a function, the argument of which is a predicate verifying some condition:

        def apply-test(test){
            ./v:eval(test);
        }

        (1, 2, 3) / apply-test(%. [. > 1]);    # = (2, 3)
        (1, 2, 3) / apply-test(%. [$_i = 1]);  # = (2)

The same technique is convenient for handling the general structure of ontologies. The basic constructs of Libretto are focused on work with objects. Evaluation prevention allows Libretto to work with classes and properties as data values. In particular, the language contains a number of built-in functions responsible for this:

        (%JukeboxSong)/v:get-direct-superclasses();  # = (%Song, %JukeboxItem)
        (%genre)/v:domain();    # = (%Song)

        (%*)/v:create-class("ACapellaSong");  # = (%ACapellaSong)
        (%Song)/v:add-subclass(%ACapellaSong);  # = (%Song)

Here %* denotes the default ontology in which a new class ACapellaSong is generated. Note that the pair % and v:eval/1 implement the classical lambda abstraction/application scheme. It interprets expressions as unary functions, in which the context values play the role of the only argument.

27 Indexing

Indexing functions are Libretto's built-in functions, which construct data indices (vocabularies). Indices significantly improve efficiency by providing fast access to indexed data. Indexing functions use quoted expressions as arguments. Let us add lyrics to the jukebox. For this, the class Song can be augmented with a new t-property:
    class Song {lyrics v:string};
For example, the first two verses can be added to &HeyJude:
    &HeyJude {lyrics =
        "Hey Jude, don't make it bad.\n"
        "Take a sad song and make it better.\n"
        "Remember to let her into your heart,\n"
        "Then you can start to make it better.\n\n"

        "Hey Jude, don't be afraid.\n"
        "You were made to go out and get her.\n"
        "The minute you let her under your skin,\n"
        "Then you begin to make it better."
        };
Now we can index the text lines by the words occurring in them, that is, define an explicit mapping from the words to the lines. The next single-lined query does this work:
    v:set-index("lines", Song/lyrics/v:split("\n", ""), %., %v:split("[\\s\\n\\.,]", ""));
The function v:set-index/4 creates a new index map (implemented as a hash map) named "lines". Its arguments are
"lines"
determines the name of the generated map;
Song/lyrics/v:split("\n", "")
determines the set of basic elements for indexing;
%.
the quoted expression applied to each basic element, to determine the indexed values;
%v:split("[\\s\\n\\.,]", "")
the quoted expression applied to each basic element, to determine the indexing values
The function starts with evaluating the second argument in order to obtain the basic elements. These elements are used for index construction. Then the expression in the third argument is applied to each basic element to get its indexed values. The fourth argument calculates for each basic element its indexing values. In the example the basic and indexed elements are the separated lines of songs. To get indexing values the path in the fourth argument splits each line into separate words:
    "Hey Jude, don't make it bad." / v:split("[\\s\\n\\.,]", "");    
                    # = ("Hey", "Jude", "don't", "make", "it", "bad")

To apply the constructed index, the function v:get-index/2 is used. For instance,

    v:get-index("lines", "make");
        # = ("Then you begin to make it better.", 
          #   "Take a sad song and make it better.", 
          #   "Then you can start to make it better.", 
          #   "Hey Jude, don't make it bad.")
The query returns the lines, which contain the word "make".

In the example above the sets of basic and indexed elements coincide. But it is not always the case. Suppose, we need a glossary with articles describing term meanings:
    class GlossaryArticle {
	    term v:string;
	    description v:string
    };
This is an example of an article definition:
    GlossaryArticle &index {term = "index"; description = "A mapping."};
Then the following index is useful for fast access to the term descriptions:
    v:set-index("glossary", GlossaryArticle, %description, %term);
    v:get-index("glossary", "index"); # = ("A mapping.")

v:set-index/4 allows not only constructing, but also augmenting indices. For instance,

    v:set-index("lines", "This line contains the word 'song'", %., %"song");
    v:get-index("lines", "song"); 
            # = ("Take a sad song and make it better.", "This line contains the word 'song'")
Also it is possible to get all indexing keys
    v:get-index-keys("lines");
            # = ("to", "song", "Jude", "can", "sad", "Then", "made", "Remember", 
               #  "skin", "minute", "The", "were", "her", "be", "You", "it", "heart", 
               #  "get", "let", "a", "Take", "your", "under", "into", "don't", "you", "bad",
               #  "begin", "and", "afraid", "start", "better", "Hey", "make", "out", "go")
and remove the keys together with their values:
    ("make", "a") / v:remove-index-key("lines");
    v:get-index("lines", "make");    # = ()
Finally, we can remove the index itself
    v:delete-index("lines");

28. Sorting

The elements of context sequences are always placed in some order. There is a special sorting operator in Libretto <<Exp-1,... Exp-k>>, which sorts the context sequence in accordance with the values of Exp-i: it sorts the context sequence in the ascending order of Exp-1. For those elements, for which the values of Exp-1 are equal, it sorts in ascending order of Exp-2, etc.:

   ("def", "ghi", "abc")<<.>>;      # = ("abc", "def", "ghi")
   (3, 5, 1, 4, 2)<<–.>>;           # = (5, 4, 3, 2, 1)
   *<<title>>;                      
         # = (&HardRock, &HeyJude, &Rock, &RockClock, &RockAndRoll, &Rockability)
   Genre<<~subgenre/title, title>>; 
         # = (&HardRock, &RockAndRoll, &Rockability, &Rock, &f:Skiffle)

In the last query the genres are sorted by the titles of their super-genres, and if these titles are equal, then by the titles of the genres. In the 'jukebox' ontobase, this query separately sorts the genres with the super-genre Rock, then the sub-genre of Rock'n'Roll, and finally the genres with no super-genre.

Arbitrary expressions can be used as arguments of the sorting operator, including built-in and user-defined functions. Suppose, we need to sort section titles in accordance with their enumeration. Let us take, for instance, some titles of this document:

        ("9 Data Types", "8 Modifiers, Predicates and Sequences", "10 Asterisks")

If to sort the titles as strings, the order will not be satisfactory:

        ("9 Data Types", "8 Modifiers, Predicates and Sequences", "10 Asterisks")<<.>>;
        
            # = ("10 Asterisks", "8 Modifiers, Predicates and Sequences", "9 Data Types")

since '8' is lexicographically greater than '1' (the lexicographic order is a default order for strings). Let us introduce a function

	function get-number()
                v:matches("(\\d*).*", "")/v:group(1) + 0;

This function cuts out first digit symbols from a context string and converts them to an integer. The trick with adding 0 provides the conversion from the string to the corresponding integer. Now,

	("123aaa")/get-number();        # = (123)

and

	("9 Data Types", "8 Modifiers, Predicates and Sequences", "10 Asterisks")<<get-number()>>;
        
			# = ("8 Modifiers, Predicates and Sequences", "9 Data Types", "10 Asterisks")

29. Annotations

Besides the basic information about the domain of discourse sometimes it is necessary to store auxiliary data about the ontology entities. In such cases annotations are used, which are the string pairs of the form key/value. In particular, annotations are convenient for the description of how this or that entity (say, the elements of a certain class) must look in a GUI. There are many other situations, in which annotations are quite useful. We recommend to follow the principle: it is preferable to keep in annotations only secondary and auxiliary information, and not to store in them the information about the domain of discourse itself. The examples of annotations:

        &HeyJude/v:annotate("importance", "high");             # = (true)
        &HeyJude/v:get-annotation("importance");               # = ("high") 

Those entities, which can not be directly handled by Libretto (like classes and ontologies), are handled via the quoted expressions or URIs:

        (%Song)/v:annotate(("layout", "color"), ("grid-based", "green"));        
                                                                        # = (true)
        (%Song)/v:get-annotation("layout");      # = ("grid-based")
        ("http://music.ontobox.org/jukebox#Song")/v:get-annotation("color")        # = ("green")

30. Question Marks

Variables $_0..$_9 play in Libretto a number of special roles. In particular, they are used in combination with the question mark: i-th occurrence of the question mark corresponds to the value of the i-th variable. This construct is used in a number of web-programming techniques.

                $_0 = 1;
                $_1 = 2;
                $_2 = 3;
                $_3 = 4;
                $_4 = 5;
                $_5 = 6;
                $_6 = 7;
                $_7 = 8;
                $_8 = 9;
                $_9 = 10;

                (?,?,?,?,?,?,?,?,?,?);  # = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
                *[? = ?];               # = ()

                function sum(x, y) 
                        x + y;
                sum(?, ?);                    # = (3)

                $_0 = (&RockClock, &HeyJude);
                $_1 = 200;
                ?[jbcode > ?];                  # = (&HeyJude)           
        

31. Libretto Expressions

This section gathers the information about the expressions of Libretto. An expression is a basic syntactic unit serving for the construction of queries and programs. The expressions are subdivided into atomic and compound expressions.

Atomic expressions:

typeexamplea path step?comment
int1 20 -3 -
long100000000000 -
float1.5 -200.8 -
string"this is it" 'hello' - can be a rightmost step
дата"2010-02-20" - can be a rightmost step
variableX variable-1 +
asterisk* *:* f:* +
dot. .. +
object name&john &book +
property namename spouse +
class namePerson Genre +
question mark? +

Not any expression can play the role of a path step, therefore it is indicated if this type of expressions can play this role.

Compound expression:

typeexamplea path stepcomment
termx = age + 2*he - operations as and at can be used in steps
predicate[not child] +
sequence(name, x+1) +
modifier{age = 20, name = "Tim"} +
function callv:contains("haha") +
object generatorPerson &john       &_ +


Operations:

Terms in Libretto are constructed with the following operations:

operationтипprioritycomment
+infix50plus
-infix50subtraction
/-/prefix20unary minus
*infix30multiplication
divinfix25division
modinfix25modulo
=infix100equality
!=infix100non-equality
<infix100less
>infix100greater
<=infix100less or equal
>=infix100greater or equal
andinfix200logical and
orinfix250logical or
notprefix150logical negation
atinfix10index operator
asinfix10variable assignment
~prefix1property inversion
=infix15assignment
.=infix15adding to the beginning
+=infix15adding to the end
--postfix15delete
!!infix15"for all"
!infix15"for all"
/infix10path step delimeter
%prefix500evaluation prevention
ifprefix --- conditional expression
forprefix --- loop

32. An Ontobase Management Demo

Let us consider the behavior of modifiers in more detail. We create for this a new ontology with a class in it:
	ontology . "http://ontobox.org/demo-management";

	class CLS {
		int v:int, 
		str v:string,
		obj {CLS}
	}
The class CLS contains two t-properties with integer and string values, and one o-property, the values of which are objects of the same class CLS. Let us generate an object &obj1 and work with its t-property str:
	CLS &obj1 {
		str = "ohohoh", 
		str = "hohoho", 
		str = "<< - >>"
	};
Since = is the destructive assignment operator, in str only one value remains, which appears in the last assignment:
	&obj1/str;    # = ("<< - >>")
In order to add values before and after the existing value, we use .= and += :
	&obj1 {
		str .= "ohohoh",
		str += "hohoho",
		str .= "uhuhuh",
		str += "huhuhu"
	};						# = (&obj1)
As the result we get five values of str:
	&obj1/str;    # = ("uhuhuh", "ohohoh", "<< - >>", "hohoho", "huhuhu")
and the initial value "<< - >>" is in the middle of the sequence. Now let us assign the integer property int:
	&obj1 {
	    int = 1
	}; 				# = (&obj1)
	
	&obj1/int;    # = (1)
Using a sequence, several values can be added at a time:
	&obj1 {
	    int .= 0,
	    int += (2, 3, 4)
	};

	&obj1/int;    # = (0, 1, 2, 3, 4)
Operator at finds a value by its index in the context sequence:
	&obj1/int at 3;    # = (3)

	&obj1/(int at 2 + int at 3);    # = (5)
In the following query plus behaves as a grouping operation, which has impact on each context element:
	&obj1/(5+int);    # = (5, 6, 7, 8, 9)
The parentheses here are necessary, because &obj1/5+int is interpreted as (&obj1/5)+int.

In order to collect modified objects we can use paths:

	&obj1 {int at 3 = 111}; 
The path int at 3 selects the third (starting with 0) value of the t-property int, which is substituted by 111. Now,
	&obj1/int;    # = (0, 1, 2, 111, 4)  
Predicates are also useful for modified objects collection:
	&obj1/str;        # = ("uhuhuh", "ohohoh", "<< - >>", "hohoho", "huhuhu")

	&obj1 {str[v:contains("ohoho")] = "{.}***"};     # = (&obj1)

	&obj1/str;        # = ("uhuhuh", "ohohoh***", "<< - >>", "hohoho***", "huhuhu")
Here the dot is used for obtaining the current (old) value, which is substituted by a new one. Now, a slightly more complicated example:
	&obj1 {str[v:matches("(.*) - (.*)", "i")] = "{v:group(2)} - {v:group(1)}"};

	&obj1/str;    # = ("uhuhuh", "ohohoh***", ">> - <<", "hohoho***", "huhuhu")
Here the modifier selects the strings corresponding to the regexp pattern "(.*) - (.*)" with two groups in parentheses. This pattern is satisfied by only one value "<< - >>". The first group in it matches with the substring "<<", and second is by substring ">>". Then the context string is substituted by a string, in which the first and the second groups are exchanged.

The following modifier adds two new values to the t-property str after the values containing a substring "hoho":

	&obj1 {str[v:contains("hoho")] += ("!!{.}", "??{.}")};   # = (&obj1)

	&obj1/str;    
		# = ("uhuhuh", "ohohoh***", "!!ohohoh***", "??ohohoh***", 
		#		">> - <<", "hohoho***", "!!hohoho***", "??hohoho***", "huhuhu")
In the following example the delete operator -- works:
	&obj1 {str[v:contains("hoh")] --};   # = (&obj1)
This modifier has deleted all values of str, which contain a substring "hoh":
	&obj1/str;    # = ("uhuhuh", ">> - <<", "huhuhu")
We can delete all the values of a property at a time:
	&obj1 {str --};   # = (&obj1)

	&obj1/str;    # = ()
Now let us work with the o-property obj, and add to it two new objects:
	CLS &obj2;
	CLS &obj3;

	&obj1 { obj = (&obj2, &obj3) };          # = (&obj1)

	&obj1/obj;                               # = (&obj2, &obj3)
We can modify the values mediately, via a chain of o-properties. The following modifier changes the values of the property obj in the objects &obj2 and &obj3:
	&obj1/obj {obj = &obj1/obj};   # = (&obj2, &obj3) 
because &obj2, &obj3 are the values of the path &obj1/obj. Now:
    &obj2/obj;                  # = (&obj2, &obj3)
    &obj3/obj;                  # = (&obj2, &obj3)
    &obj1/obj at 1/obj;	        # = (&obj2, &obj3)

33. Input/Output

Libretto contains a set of builtin functions responsible for interaction with the outer world. It includes standard functions like v:print/0, v:println/0, v:read/1, v:write/2, v:read-lines/1 as well as special export/import functions. In particular, the functions v:mvx2box/1 and v:box2mvx fulfill import/export operations from/to the ontobase to/from a portable dump, which completely reflects the context of the ontobase. In a similar way we can export the ontobase content in a Libretto script. This is done by the functions v:box2libretto/0 и v:tbox2libretto/0. The latter exports only the ontobase structural data (ontobase 'patterns' without objects), and this is useful for forming standard solutions, knowledge portability and reusability.

Another group of functions contains disk-map functions overviewed in section 9.5.

34. XML and HTML Documents

Libretto is well shaped for semistructured-data management, in particular, XML and HTML documents. The idea here is to have a standardized ontology describing the structure of XML documents, which can be employed in all applications of Libretto using XML/HTML. A standardized specification of this ontology is represented as a Libretto script. In it, each document's entity (an element, an attribute, a text etc.) is represented as a separate ontology object. This ontology, which has the URI

        http://xml.ontobox.org/
is predefined in OntoBox.

Though this specification script is syntactically correct, do NOT load it into the Libretto interpreter. All classes and properties described in it are predefined in OntoBox, so an 'already-defined' error will be generated.

It is highly recommended to employ this XML ontology for the development of applications using XML and HTML. This ontology not only supplies the user with special Libretto's document handling tools, but also provides a unified space of Ontobox & Libretto applications working with XML/HTML documents.

Libretto contains four built-in read/write functions for XML documents: v:read-xml/2, v:read-xml-string/1, v:write-xml/2 and v:write-xml-string/1. For instance,

    ontology xx "http://my.xml";
    x = "<a>abc</a>" / v:; # = (&_1269923044901-0)

v:read-xml-string/1 imports the XML document from the context string, and converts it into the set of xml-objects. The function's only argument must be an identifier of the ontology, in which the objects of this XML document to be stored.

The ontology can be represented in the argument by either a quoted expression like %xx* or an ontology prefix like "xx", or an ontology URI like "http://my.xml". The prefix can start with '@', e.g. "@xx", and this means that if an ontology with the prefix "xx" does not exist, then it must be created with some surrogate URI.

Now we can operate with the imported document:

    x/x:root/x:name; # = (a)
    x/x:root[x:name="a"]/x:content/x:text; # = (abc)	

The function v:read-xml/2 reads an XML document from a file or an URL. For instance, the following query reads the well-known OWL description of the wine ontology in the XML/RDF format:

    v:read-xml("http://www.w3.org/TR/owl-guide/wine.rdf", "@wine");
This document is stored in the newly created ontology with the prefix "wine". Now, for instance, we can count the number of elements in this document:
    wine*/x:Element/v:Count();    # = (1509)    

The functions v:write-xml/2 and v:write-xml-string/1 write an XML document in a file and string, respectively. The stored document is established by its root in the second argument of v:write-xml/2 and the first argument of v:write-xml-string/1:

    v:write-xml-string(x); # = ("<a>abc</a>")
    v:write-xml("file.xml", x); # = (true) 

Libretto also supports two functions for reading HTML documents: read-HTML/3 and read-HTML-string/1:

    ontology . "http://my.html";
    h = "<body><p>abc</body>" / v:read-html-string("");  # = (&_1270046685533-1857)
Actually, this function converts the input HTML-text into the corresponding XHTML document:
    v:write-xml-string(h); # = ("<html><head/><body><p>abc</p></body></html>")

The function v:read-html/3 demands the explicit indication of encoding:

    v:read-html("http://ontobox.org", "@obox", "utf-8"); # = (&_1270526811246-1932)

User-defined functions can make document handling more elegant and compact:

    function get-el(name)  
        x:Element[x:name = name];

    function get-text()   
        if (x:Text) x:text else x:content/get-text;

    wine* / get-el("label") / get-text();  # = ("vin", "wine", "Wine Ontology");
    wine* / get-el("label") at 2 / get-text();  # = ("Wine Ontology")

    function get-attr(name)   
        . / x:attributes[x:name=name] / x:value;

    wine* / get-el("label")[get-attr("lang") = "fr"] / get-text();  # = ("vin")

35. Embedded Languages

Libretto allows for incorporation in its programs of the codes in other languages, e.g. XML, JSon or Ruby. The expressive power of ontologies makes it possible to easily simulate in them the diverse data structures. A text in the embedded language is translated into objects of a special ontology, which describes the structure of this embedded language. For instance, in Libretto XML code can be embedded:
    address = "Paris, France";

    paris-xml = 
        xml::__
            <address type = "surface">
                |address|
            </address>__;

An XML text is included in the following form: xml::__<XML code>__. The insertion starts with the language identifier 'xml::'. The double colon :: is the indication of the inserted code. It is followed by the terminal sequence, which terminates the embedded code. As a terminal sequence we chose here __, though here can stand any other symbol, or word, like abc.

The implementation of an embedded language is external w.r.t. the Libretto interpreter, which interacts with this implementation via a special programming Java-interface. A concrete structure of objects, in which an embedded code is translated depend on this implementation. In particular, the XML implementation generates an ontology a sketch of which is established in the next Libretto- program:

    ontology x "http://ontobox.org/libretto/embedded/xml";
	
    class x:Entity {map:Map};
    class x:Element {map:Map};
    class x:Attr {map:Map};
    class x:Text {map:Map};

    # наш код:
    address = "Paris, France";
    paris-xml = x:Element & {
                      attr = x:Attr & {	
                                  key = "type",
                                  value = address
                              }
                      contents = "Paris, France"
                  };
As we see, the representation in the form of the XML text is an actually syntactic sugar, with normal ontology objects behind, described with the prefix x. Nevertheless this feature is very useful and comfortable.

The parser of the described implementation of XML allows us to incorporate between | ... | the codes of Libretto within XML texts. Special built-in functions are included in the implementation, for instance,

        paris-xml/x:to-xml();  
                # = <address type = "surface">
                #       Paris, France
                #   </address>

Here x:to-xml/0 is a built-in function translating the XML structure into a string. It together with the prefix of the XML ontology x, defined by the implementation of Libretto.

The user can augment Libretto with any necessary embedded language. For this, it is necessary to develop a special Java-class on the basis of the programming interface provided by Libretto.