ISO (the International Organization for Standardization) and IEC (the International Electrotechnical Commission) form the specialized system for worldwide standardization. National bodies that are members of ISO or IEC participate in the development of International Standards through technical committees established by the respective organization to deal with particular fields of technical activity. ISO and IEC technical committees collaborate in fields of mutual interest. Other international organizations, governmental and non-governmental, in liaison with ISO and IEC, also take part in the work. In the field of information technology, ISO and IEC have established a joint technical committee, ISO/IEC JTC 1.
International Standards are drafted in accordance with the rules given in the ISO/IEC Directives, Part 2.
ISO/IEC 18048 was prepared by Joint Technical Committee ISO/IEC JTC 1, Information Technology, Subcommittee SC 34, Document Description and Processing Languages.
This International Standard defines a query language for Topic Maps known as TMQL (Topic Maps Query Language). This draft was informed by TMQLuc[2] and TMQLreq[1] and is for review for interested parties.
This International Standard defines a formal language for accessing information organized according to the Topic Maps paradigm. This document specifies syntactic rules to form valid query expressions to extract information from a Topic Maps instance and also provides an informal and a formal semantics for every syntactic form.
To constrain the interaction and information flow between a querying application and a TMQL query processor (short: processor) this International Standard also describes an abstract processing environment, loosely defines the passing of parameters into the query process and the exchange of result values. This environment also includes a minimal, predefined set of functions and operators every conformant processor must provide.
This International Standard does not define an API (application programming interface) to interact with query processors and also refrains from naming certain error conditions. It also remains silent on other implementation issues, such as optimization or error recovery.
The following referenced documents are indispensable for the application of this document. For dated references, only the edition cited applies. For undated references, the latest edition of the referenced document (including any amendments) applies.
Each of the following documents has a unique identifier that is used to cite the document in the text. The unique identifier consists of the part of the reference up to the first comma.
Unicode, The Unicode Standard, Version 5.0.0, The Unicode Consortium, Reading, Massachusetts, USA, Addison-Wesley Developer's Press, 2007, ISBN 0-321-48091-0, http://www.unicode.org/versions/Unicode5.0.0/
TMDM, ISO 13250-2 Topic Maps — Data Model, ISO, 2006, http://www.isotopicmaps.org/sam/sam-model/
TMRM, ISO 13250-5 Topic Maps — Reference Model, 2008, http://www.isotopicmaps.org/tmrm/
CTM, ISO 13250-6 Topic Maps — Compact Syntax, 2008-05-15, http://www.isotopicmaps.org/ctm/ctm.html
XML 1.0, Extensible Markup Language (XML) 1.0, W3C, Third Edition, W3C Recommendation, 04 February 2004, http://www.w3.org/TR/REC-xml/
XTM 2.0, Topic Maps — XML Syntax, ISO 13250: Topic Maps, 2006-06-19, http://www.isotopicmaps.org/sam/sam-xtm/
XSDT, XML Schema Part 2: Datatypes Second Edition, W3C, W3C Recommendation, 28 October 2004, http://www.w3.org/TR/xmlschema-2/
RFC3986, RFC 3986 - Uniform Resource Identifiers (URI): Generic Syntax, 2005, http://www.ietf.org/rfc/rfc3986
RFC3987, RFC3987 - Internationalized Resource Identifiers (IRIs), 2005, http://www.ietf.org/rfc/rfc3987.txt
RegExp, IEEE Std 1003.1, 2004 Edition, 2004, The Open Group Base Specifications Issue 6, http://www.opengroup.org/onlinepubs/009695399/mindex.html
The syntax is defined on three levels:
At the token level this International Standard makes use of regular expressions [RegExp] to specify case-sensitive character patterns for valid terminal symbols. They are enclosed in slashes (//) to differentiate them from constant tokens. The abbreviation \w is used for the character class [a-zA-Z0-9_] (alphanumeric characters and the underscore character) and \d for digit characters [0-9]. Tokens are rendered in bold and are underlined.
Tokens not defined in the grammar are those for binary infix and unary prefix operators; these are specified in the predefined environment together with their function counterparts (Annex A).
As usual, character symbols are either delimiting or not. The list of delimiting symbols is provided in Annex B. Any other symbol is not delimiting and whitespace characters (blank, tab and newlines) must be used for separation. Whitespace characters are allowed everywhere between two tokens; this is not encoded explicitly in the syntax. Whitespace characters are insignificant except within strings, XML fragments and within a CTM stream.
The canonical syntax level is defined using a context-free grammar ([XML 1.0]) with the following conventions: For A * we use { A }, for A ? we use [ A ] and for ( A ( ',' A )* )? (a comma-separated, possibly empty list) we use < A >.
For alternatives we use | between the choices. The grammar is built in such a way so that the lexical order of choices resolves any ambiguities in that it always prefers constant tokens over regular expression tokens.
Productions for non-terminals are numbered for reference.
On top of the canonical syntax, a non-canonical syntax level is introduced to reduce the syntactic noise in actual query expressions. These abbreviations are defined via an additional grammar production whereby a term (a sequence of terminals and non-terminals) on the right-hand side of a production is expanded (using term substitution ==>) into another term. Any abbreviated form is semantically equivalent to its expanded form, so shortcuts do not add any computational complexity to the language but are only added for convenience. These mappings are numbered with letters for reference.
Comments are fragments of the character stream which are ignored by any processor. Comments are allowed where whitespace characters are allowed and are introduced by a hash (#) character (1.) at the very beginning of a line or (2.) a hash character following a whitespace character outside a string or XML fragment. Comments reach until the end of the current line or until the end of the text stream, whichever comes first. Comments are not made explicit in the grammar.
Annex C contains the complete language syntax. This grammar was produced for human consumption and is not optimized for a particular parsing method or for a minimum of non-terminals.
The semantics of TMQL is defined in prose throughout the text. Such prose is highlighted in the text using a vertical sidebar (as this paragraph); it — by its nature — is ambiguous and is only meant to support the human reader.
The informal semantics is superceded by the formal semantics which is detailed in Clause 7.
A TMQL processor assumes that the environment which provides access to the queried map(s) will also provide type transitivity and type membership:
For the binary relation between a subtype and a supertype the interpretation and representation in [TMDM] (7.3) is adopted and extended.
Accordingly, for transitivity a TMQL processor will assume that if a concept B is a supertype of A, and C is a supertype of B, then also C is a supertype of A. A processor will also interpret such relation as reflexive so that every type is a subtype and a supertype of itself.
For a transitivity-free operation any subclass relationship between concepts carries no inferenced meaning.
For the binary relation between an instance and a type the interpretation and representation in [TMDM] (7.2) is adopted. Accordingly, a processor will assume that if a concept is an instance of a type C, that very concept is also an instance of all supertypes of C.
For a transitivity-free operation any instance relationship between concepts only applies to these concepts.
Apart from this, a TMQL processor is entailment neutral, i.e. it leaves it to the environment to perform any inferencing.
This International Standard makes the following prefix references to external vocabulary:
http://psi.topicmaps.org/iso13250/model/
This is the namespace for the concepts defined by TMDM (via the TMDM/TMRM mapping).
http://www.w3.org/2001/XMLSchema#
This is the namespace for the XML Schema Datatypes.
http://psi.topicmaps.org/tmql/1.0/
Under this prefix the concepts of TMQL itself are located.
http://psi.topicmaps.org/tmql/1.0/functions/
Under this prefix user-callable functions of the predefined TMQL environment are located.
http://purl.org/dc/terms/
Under this prefix Dublin Core elements are located.
Each of these prefixes are manifested as topics in the environment map as described in 6.3.1.
Constants are either atomic values from the sets of predefined basic data types (Annex A), or they can also be references to items in the current effective map (6.2):
| [1] | constant | ::= | atom | item-reference |
Atoms are literal values, such as strings, integer or dates which can be used as constants. This International Standard adopts a list of primitive data types from [CTM] together with operators for these types.
| [2] | atom | ::= |
| |||||||||||||||||||||
| [3] | undefined | ::= | undef | |||||||||||||||||||||
| [4] | boolean | ::= | true | false | |||||||||||||||||||||
| [5] | number | ::= | decimal | integer | |||||||||||||||||||||
| [6] | decimal | ::= | /[+-]?\d+(\.\d+)?/ | |||||||||||||||||||||
| [7] | integer | ::= | /[+-]?\d+/ | |||||||||||||||||||||
| [8] | date | ::= | ... see http://www.w3.org/TR/xmlschema-2/#date, 3.2.9.1 Lexical representation ... | |||||||||||||||||||||
| [9] | dateTime | ::= | ... see http://www.w3.org/2001/XMLSchema#dateTime, 3.2.7.1 Lexical representation... | |||||||||||||||||||||
| [10] | iri | ::= | " QIRI " | |||||||||||||||||||||
| [11] | string | ::= | /"([^"]|\")*"/ | /'([^']|\')*'/ |
Obviously, decimal patterns are preferred over the shorter integer patterns. Strings may use either quotes (") or apostrophes (') as terminators. Any terminator can be escaped within the string with a leading backslash (\). Strings can reach over several lines. IRI literals are specialized strings; they must also be wrapped in quotes.
The following are valid atoms (type in brackets):
23 # integer 3.1415 # decimal "Hello World" # string 'Dante\'s Hell' # string 2005-10-16T10:29Z # date "http://example.org/something" # iri undef # undefined |
The following are invalid atoms:
3,14 # comma instead of dot - 273.15 # blank after the sign. |
For all types, an IRI (or a QName) can be explicitly provided to indicate the data type. This allows implementations to offer additional primitive data types (Clause 8).
The following are also valid atoms (type in brackets):
"42"^^xsd:integer # integer "http://example.org/something"^^xsd:string # string "http://example.org/something"^^xsd:anyURI # iri "w3 0wn u"^^what:ever # what:ever |
For references either absolute IRIs or QNames can be used:
| [12] | QIRI | ::= | IRI | QName |
| [13] | IRI | ::= | /([^<>'{}|^`] - [#x00-#x20]])*/ |
| [14] | QName | ::= | prefix identifier |
| [15] | prefix | ::= | /\w+:/ |
| [16] | identifier | ::= | /\w[\w\-\.]*/ |
IRIs are strings not containing angle brackets, curly brackets, the pipe character (|) and the caret (^) as well as no non-printable characters.
For QNames the prefix (without the trailing colon) has to be declared as such by the environment (6.3). No blanks between the prefix and the following identifier are allowed.
Prefixes cannot be empty, so there is no mechanism to use identifiers relative to some document base.
If the item reference is a QName, then the QName prefix (without the trailing colon :) is interpreted as an topic item identifier for a topic of type tmql:ontology in the effective map; it is an error if no such topic exists. If no subject indicator for that ontology topic exists, then an error will be flagged. Otherwise one such subject indicator — together with the identifier in the QName — is used to construct an absolute IRI according to the rules in [RFC3986] (5.2, Relative Resolution).
In the map to be queried (effective map, 6.2) Topic Map items can be named directly via an item identifier or a subject identifier, as provided by an IRI or a QName. The latter is optionally wrapped into <>:
| [17] | item-reference | ::= | identifier | QIRI | < QIRI > |
If the item reference is an identifier then this identifier is interpreted as an topi item identifier ([TMDM], Clause 5.1) for a topic in the effective map. The result is then this topic item; if no such topic exists, an error will be flagged.
The following expression first identifies the topic with the item identifier jack and then retrieves all its names:
jack / name |
While convenient, item references are not a robust way to identify topics. TMDM processors are not constrained how they assign item identifiers to items ([TMDM], Clause 5.1).
If the item reference is a QName, then first that is expanded into an absolute IRI according to 4.2. That absolute IRI is interpreted as subject indicator for a topic in the effective map. If no such topic exist an error will be flagged.
The item reference http://example.org/something/ is interpreted as subject identifier.
tm:subject can be used when the particular topic is not relevant, or unknown. As any Topic Map item represents an instance of subject by definition, this makes predicates such as is-located-in (tm:subject : $place, location: paris) more robust.
Each navigation step is interpreted within the effective map (6.2). Navigational axes are derived from the structure of a Topic Map instance [TMDM] and can either be followed in forward (>>) or in backward (<<) direction:
| [18] | step | ::= | ( >> | << ) axis [ anchor ] | ||||||||||||||||||||||||||||||||||||
| [19] | axis | ::= |
|
The optional anchor adds control information which is useful with some axes, but not others. If it is missing tm:subject will be assumed.
When the anchor is evaluated, it must evaluate to a topic item and is interpreted as type. Then in all navigation steps the current setting for type transitivity (6.3.2) is honored.
Given a map and a single value (be it an atom or an item in the map), the following axes are defined:
In forward direction this step computes all types of the value according to 3.3. In backward direction this step produces all instances of the value. The optional item has no relevance.
person << types produces all instances of the concept person, say, jack, jill, etc.
Additional Notation: Asking for all instances of an item is the inverse of asking for types.
| [A] | step | ::= | >> instances |
| ==> | << types | ||
There is no navigation axis for deriving the data type of an atom, but the function has_datatype (Annex A) can be used.
In forward direction this step computes all supertypes of the value according to 3.3. In backward direction this step produces all subtypes of the value. The optional item has no relevance.
person >> supertypes produces all supertypes of the concept person, say, human, mammal, etc. depending on the used taxonomy.
Additional Notation: Asking for all subtypes of an item is the inverse of asking for all supertypes.
| [B] | step | ::= | >> subtypes |
| ==> | << supertypes | ||
If the value is an association item, in forward direction this step computes all role-playing items of that item. The optional item specifies the type of the roles to be considered. If a playing topic plays several roles in such an association item, then it appears as many times in the result (multiset interpretation).
If the value is a topic item, in backward direction this step computes all association items in which that topic plays a role. If a role playing topic plays several roles in one and the same association, this association will appear as many times. The optional item specifies the type of the roles to be considered.
The following chain of navigation steps finds first all associations where a topic item (bound to $p) is playing the role member. Then for all of these the players for role group are retrieved.
$p << players member >> players group |
Additional Notation: Following shorthand notations for movements involving association items are available:
The following navigation chain finds first all associations where a topic item (bound to $p) is playing the role member. Then for all these the players for role group are retrieved.
$p <- member -> group |
If the value is an association item, in forward direction this step computes all role-typing topics. Multiple uses of the same role type in one association causes multiple results. The optional item has no relevance.
If the value is a topic item, in backward direction this step computes all association items where that topic is the role type. Multiple uses of one topic as role in one association causes multiple results. The optional item identifier has no relevance.
The following navigation finds first all involvements of jack as a member and then all roles in these associations:
jack << players member >> roles |
If the value is a topic item, in forward direction this step computes first all associations where the topic plays a role. The type of the associations is constrained by the optional item. The overall result of this navigation is then a sequence of all players of these associations, whereby the incoming topic is deducted once from that sequence. In backward direction the result sequence will always be empty.
The following navigation finds all co-authors of jack:
jack >> traverse co-authors |
Additional Notation: For traversing over associations of a certain type the following shortcut is introduced:
The above example can be rewritten as jack <-> co-authors.
If the value is an association item, in backwards direction this step computes all players being an instance of the type given by the optional item. The result of this step is a sequence of all association items minus the incoming one.
The following navigation finds all associations which share a topic of type person with $a:
$a << traverse person |
If the value is a topic item, in forward direction this step computes all names and occurrences of that topic which are subtypes of the optionally specified item. The result is a sequence of name and occurrence items.
If the value is a name or an occurrence item, in backward direction this step computes the topic to which the name or the occurrence is attached. The optional item can be used to constrain the type of the items one is interested in.
The following navigation finds all occurrences of jack:
jack >> characteristics tm:occurrence |
The following navigation finds all nicknames of jack:
jack >> characteristics nickname |
The following navigation finds all names and occurrences of jack:
jack >> characteristics tm:subject |
In forward direction, this navigation leads from characteristics (names and occurrences) and association items to their scope.
In backward direction, this navigation leads from a topic to all associations and characteristic items in that scope. The optional item has no relevance.
Additional Notation: To extract scoping information the following shorthand can be used:
| [F] | step | ::= | @ |
| ==> | >> scope | ||
If the value is a topic item, in forward direction this step retrieves all subject locators (subject addresses) of this item. If the value is an IRI, in backward direction this step retrieves all topic items which have this IRI as subject locator. The optional item has no relevance.
Additional Notation: For identifying topics via a subject locator the following shortcut is introduced:
| [G] | step | ::= | = |
| ==> | << locators | ||
If an XTM instance would be stored in a file file:/maps/dictators.xtm, then the expression file:/maps/dictators.xtm = would identify the document itself, but only if a topic with that subject locator exists in the map. If not, the result will be empty.
If the value is a topic item, in forward direction this step retrieves all subject indicators of this item. If the value is an IRI, in backward direction this step produces the topic which has this IRI as subject indicator. The optional item has no relevance.
Additional Notation: For identifying topics via a subject identifier, the following shortcut is introduced:
| [H] | step | ::= | ~ |
| ==> | << indicators | ||
In a map about dictators the expression "http://en.wikipedia.org/wiki/Stalin" ~ would indicate the subject Stalin and would return a topic item if such a topic existed in the map with the IRI as subject indicator.
The expression "file:/maps/dictators.xtm" ~ indicates the subject which is that map itself. If a topic with such an subject identifier exists in the effective map, that topic item is the result.
This is in contrast to using directly a item reference (4.3), such as file:/maps/dictators.xtm (without the quotes and without the navigation). In that case it is an error if no such topic exists within the effective map with that subject identifier.
If the value is a topic item, then in forward direction this steps finds one item identifier and produces a string. The optional item has no relevance.
Processors should provide a consistent selection of item identifiers during one evaluation.
Additional Notation: To retrieve the topic item identifier as string the following shortcut exists:
| [I] | step | ::= | ! |
| ==> | >> item | ||
If the value is a string, then in backwards direction this steps finds one topic item which has this string as item identifer. The optional item has no relevance.
If the value is a topic item, then in forward direction this steps finds the association, name or occurrence item which is reified by this topic. If the topic reifies a map, then all items in that map will be returned and the context map %_ will be set to this map for the remainder of the directly enclosing TMQL expression. The optional item has no relevance.
If the value is an association, a name or an occurrence item, then in backward direction this step finds any reifying topic.
Additional Notation: To zoom into an association, characteristics or a whole map the following shorthand exist:
| [J] | step | ::= | ~~> |
| ==> | >> reifier | ||
If the topic stalin-dictatorship would reify an association where stalin plays the role dictator, then the expression stalin-dictatorship ~~> >> players dictator would render the topic item stalin.
To find all items in the map stored in the file /maps/dictators.xtm the expression file:/maps/dictators.xtm ~~> can be used in the FROM clause.
Additional Notation: To zoom out of an association, characteristics or a whole map the following shorthand exist:
| [K] | step | ::= | <~~ |
| ==> | << reifier | ||
If the value is a name or occurrence item, in forward direction this step schedules the item for atomification, i.e. marks the item to be converted to the atomic value (integer, string, etc.) within the item. The item is eventually converted into an atom according to the atomification rules (4.5). The optional item has no relevance.
If the value is an atom, in backward direction this step de-atomifies immediately the atom and returns all names and occurrences where this atom is used as data value. Also here the optional item has no relevance.
The following navigation finds all homepage URLs of jack:
jack >> characteristics homepage >> atomify |
The following navigation finds all topics which have a homepage occurrence with a certain URL (they cannot be names as these cannot contain URLs):
"http://myhomepages/jack" << atomify
<< characteristics homepage |
Additional Notation: If topic characteristics should be automatically atomified, the following shorthand can be used:
| [L] | navigation | ::= | / anchor [ navigation ] |
| ==> | >> characteristics anchor >> atomify [ navigation ] | ||
To extract all values of characteristics of type homepage from a topic item bound to $p, one can also write:$p / homepage.
Additional Notation: If atomic values should be automatically looked up in characteristic items of a certain type, the following shorthand can be used:
| [M] | navigation | ::= | \ anchor [ navigation ] |
| ==> | << atomify << characteristics anchor [ navigation ] | ||
The following computes all topic items where the integer 23 is used as value in an occurrence of type age:
23 \ age |
The expression "Stalin" \ name computes all topic items which have the name "Stalin".
In all combinations not listed above the atomification is the identity function.
Implementations can redefine the semantics of the atomification/deatomification function. This can be used, for instance, to convert between topic items and application-specific objects to be handled by the calling application.
If a navigation step is applied to a sequence of values it is applied to all values individually and the results are concatenated into one sequence. No ordering in these sequences is guaranteed.
Topic names and occurrences are experienced in an ambivalent way: either as items, including not only the data value, but also the scope and the type of the item; or the data value itself.
When such item is subjected to an atomify navigation step the atomic value is not immediately extracted, but the process has to be postponed. If the atomification would be done immediately, the information for scope (and type) would be lost for subsequent processing steps.
The following query expression will return only those homepage URLs where the homepage occurrence is in scope wikipedia:
select $p / homepage [ @ wikipedia ] where $p isa person |
Atomification is postponed until one of the following situations occur:
The name or occurrence is about to be passed to the environment as part of the result.
The following query expression will return the homepage URLs as IRIs (and not occurrence items):
select $p / homepage where $p isa person |
The name or occurrence is about to be compared in the process of ordering (asc or desc, 4.8.2).
The following query expression will return person name(s) and age(s), in age descending order:
select $p / name, $p / age where $p isa person order by $p / age desc |
The name or occurrence is about to be passed into a function in the process of a function invocation (4.12).
The following query expression will return person name(s) and their age which is computed from the birthday using a custom function age:
select $p / name, age ($p / birthday) where $p isa person |
This rule includes the sitation when the name or occurrence is about to be compared to an atomic value.
The following query expression will return all persons younger than 42:
select $p where $p / age < 42 |
The name or occurrence is used inside an XML fragment.
In the following, the names of each book will be atomified so that they can be inserted as text into the XML stream:
return
<books>{
for $b in // book
return
<title>{ $b / name }</title>
}</books>
|
The name or occurrence is used inside a TM fragment, except when used on positions where a name or occurrence declaration is expected ([CTM]).
The following query expression will return a TM fragment with persons from the context map who are younger than methusalem. All of these persons get the homepage characteristics copied verbatim, their name(s) is (are) embedded into the newly generated comment characteristics:
for $p in // person
where
$p / age < methusalem / age
return """
{ $p ~ }
{ $p / homepage }
comment: "the old name was : { $p / name }"
""" |
The name or occurrence item is subjected to a de-atomification step.
In the expression $book / name [ @ english ] \ title a book item is first used to extract the names. These are then filtered for english versions only. To de-atomify the value, it is first extracted from the remaining names. Only then all names and occurrences of type title are generated.
If a name or occurrence item is not scheduled for atomification, it will remain an item.
The following query expression will return the homepage occurrences themselves, not the values within them.
select $p >> characteristics homepage where $p isa person |
A simple value (anchor) is either provided as constant or as value of a variable:
Simple content is formed by an anchor followed by any number of navigation steps:
| [21] | simple-content | ::= | anchor [ navigation ] |
| [22] | navigation | ::= | step [ navigation ] |
First the anchor is evaluated in the current context. The resulting tuple sequence will then be subjected to any navigation step in lexical order. The result of the last step is the overall result of the simple content.
The simple content jack >> occurrence >> types computes all types of all occurrences a topic with topic item identifier jack.
The simple content $t / nickname computes all nicknames of a topic stored in the variable $t.
When a query expression is evaluated, it will (on successful termination) generate content. That always has the structure of a tuple sequence, i.e. a sequence of tuples consisting of atomic values, be they literal values such as integers or strings, or be they XML fragments or Topic Map items.
Content can be constructed in different ways:
| [23] | content | ::= |
|
When the binary infix operators ++, -- or == are used, -- and ++ are interpreted from left-to-right (left-associative). The operator == has the highest precedence.
Content can further be generated unconditionally with a nested query expression or conditionally with an if-then-else construct. If the ELSE branch is missing, then else () will be assumed.
Content can also be constructed as XML fragment (4.9) or as Topic Maps fragment (4.10).
Additional Notation: When the content can be produced by a simple path expression, then the curly brackets can be dropped:
| [N] | content | ::= | path_expression |
| ==> | { path_expression } | ||
If content is combined with one of the binary operators, first both content operands are evaluated in the current context. This results in two tuple sequences.
If the operator is == then the resulting tuple sequence consists of exactly those tuples which exist in both operand tuple sequences (AND semantics). Tuples are compared according to 4.8.1.
In the expression %map [ . >> types == person ] inside the filter each item from the %map is tested whether one of its types is person.
In order to achieve this, the map — a tuple sequence itself — will be iterated through. Inside the filter the first component of every individual tuple is extracted. For this the list of types is computed. That resulting tuple sequence is then compared with one containing only one singleton tuple with the item for person.
The following query expression lists all the person's names who have the same age as methusalem. Note that — even if there were several age characteristics — the condition would be satisfied if there were only a single match (exists semantics):
select $p / name where $p / age == methusalem / age |
If the operator is ++ then the resulting tuple sequence consists of all tuples from both operand tuple sequences (OR semantics).
The overall result sequence may contain duplicates.
If the operator is -- then the resulting tuple sequence consists of exactly those tuples which exist in the left operand tuple sequence, but not in the right (except semantics). Tuples are compared according to 4.8.1.
To following tuple expression can be used to find all good people in the universe:
// person -- // evildoer |
Content can also be unconditionally generated with a query expression (Clause 6) when that is wrapped inside {} brackets.
When a content is conditional (if-then-else), the result depends on the condition path expression (6.6). If that is evaluated in the current context and if that results in at least one tuple, then the overall result of the conditional is that of the content in the then branch, otherwise that of the else branch.
The following query expression returns a tuple sequence with a tuple for every person. The first value is that person's name, the second is the string voter or non-voter, depending on the age of that person.
for $p in // person
return
(
$p / name,
( if $p / age >= 18 then
"voter"
else
"non-voter"
)
) |
Additional Notation: The following shorthand allows to test for the existence of values and to use a default value otherwise:
| [O] | content | ::= | path-expression-1 || path-expression-2 | ||||||
| ==> |
| ||||||||
The following expression selects all person names. If a person does not have a name, then the special value undef will be used:
select $p / name || undef where $p isa person |
Tuples are ordered collections of simple values (atoms and items). The length of a tuple is the arity of the collection. The components of a tuple can be of different types.
A tuple without a single value is called the empty tuple. Tuples with only a single value are called singletons. Any simple value can be interpreted as singleton and vice versa.
As individual values of a tuple are ordered, the first value is assigned the index 0, the next 1, etc. Projection (6.6.3) can be used to extract one (or more) values from a given tuple. If a projection refers to an index larger or equal the length of a tuple, the extraction will result in an error.
Tuples cannot be directly denoted, only via tuple expressions. These produce zero, one or more tuples.
The tuple expression (42, "DONT PANIC") will always produce a single tuple with the integer value 42 at index 0 and the string DONT PANIC at index 1.
Tuples are only then equivalent, if they have the same length and all the values with same index are equivalent according to the equality rules of their type.
When tuples are to be compared with each other this is always done in the context of an ordering tuple. Such a tuple has the length of the longer tuple and only has components with special values asc (for ascending) or desc (for descending). If that ordering tuple is not explicit, a tuple containing only asc values is assumed.
Two tuples can then be compared with each other using the following rules:
The empty tuple is smaller than any other tuple.
The comparison of non-empty tuples is done component-wise starting with index 0.
The components at a given index are compared. If the components at this index are equivalent, then the components with the next higher index are investigated. In the latter case the tuple with the smaller length is also the smaller tuple.
If the order tuple has asc as value on a given index, that tuple with the smaller component on that index is also the smaller tuple. If the ordering tuple has desc as value on a given index, that tuple with the bigger component on that index is the smaller tuple.
If the values cannot be compared, then the ordering is undefined.
The tuple (4, "ABC", 3.14) is smaller than (4, "DEF", 2.78).
The tuple (4, "ABC", 3.14) is smaller than (4, "ABC", 2.78) under the ordering tuple (asc, asc, desc).
Tuple sequences are sequences of tuples where all tuples have identical length. Tuple sequences can be generated with tuple expressions:
| [24] | tuple-expression | ::= | ( < value-expression [ asc | desc ] > ) |
Each column contains a value expression, optionally followed by an ordering direction.
When a tuple expression is evaluated, all the value expressions are evaluated first in the current context (in no particular order). All these partial results will be interpreted as tuple sequences, whereby simple content will be interpreted as the only component of a singleton. The intermediary result is then a tuple of tuple sequences of tuples of simple content. This structure will be flattened out by building the cartesian product. The final result sequence will only contain tuples with simple values.
The tuple expression (1, // person) will return a tuple sequence with the first component the constant value 1 and the second component being a topic item of class person. For each person such a tuple exists, but there is no particular ordering.
The tuple sequence (// person, // person) contains any 2-combination of topic items of class person in the current context map.
A non-empty tuple sequence is one which contains at least one tuple. The empty tuple sequence does not contain a single tuple.
Additional Notation: The constant null represents the empty tuple sequence. It is typeless per se and is used for situations when no particular value (also not undef) should or can be used.
| [P] | tuple-expression | ::= | null |
| ==> | ( ) | ||
Tuple sequences are unordered, unless they are explicitly ordered. Ordering of tuples within a sequence implies that there is a partial ordering occurs-before defined on these tuples. Such ordering may be derived from following sources:
If the tuple sequence is generated from a tuple expression in which an order direction (asc or desc) for a component is used, then that sequence will be ordered. For this purpose, components which do not have an order direction will be assumed to have asc. Then the ordering as defined in 4.8.2 is used.
The tuple sequence specified by (// person / birthdate desc) contains tuples with only a single component. That component contains all birth date occurrences of all instances of class person. All these birth dates are sorted in descending order.
The following expression finds first all instances of person; then for each of these, the combinations of their names and ages are generated: // person ( . / name , . / age desc )
The second component of the projection carries an order direction, so that of the first component will default to asc. The resulting tuple sequence will then be ordered, first according to the name value; when there is a draw, then according to the age information, but that in descending order. Note, that this is done independently for each person.
If the tuple sequence is generated from two tuple expressions, TS1 and TS2, via the binary operator ++ using an ordered context sequence (5.5), then any tuple from TS1 must occur before any tuple from TS2. Any other existing ordering within TS1 or TS2 must be honored.
The following expression will return a list of person names:
select $p / name order by $p / age desc where $p isa person |
That list is partially sorted, namely according to the person's age. If a person has several names, then these appear in no particular order.
The following expression also selects a list of names, like in the query above. This time, however, the partial lists of names for a single person is sorted by the name. Still the individual blocks of names are sorted by the person's ages.
select $p / name asc
order by $p / age desc
where
$p isa person |
Stringification is the process of determining the string representation of a tuple or tuple sequence.
When a tuple is stringified its components are first converted into their textual representations. All these representations are then concatenated in the order of their index, separated by commas. Consequently the empty sequence will be stringified into the empty string.
When a tuple sequence is stringified then the string representations of the individual tuples will be concatenated, separated by a single carriage-return character (#x0D). If the tuple sequence is ordered, this order is also carried over into the stringified form.
XML content follows a subset of the syntactic rules given in the XML specification [XML 1.0] with one notable extension: XML content can contain query expressions (Clause 6) to generate output dynamically. These expressions must be properly nested using a balanced pair of curly brackets ({ }).
| [25] | xml-content | ::= | { xml-element } | ||||||
| [26] | xml-element | ::= | < xml-tag { xml-attribute } xml-rest | ||||||
| [27] | xml-tag | ::= | [ prefix ] xml-fragments | ||||||
| [28] | xml-attribute | ::= | [ prefix ] xml-fragments = " xml-fragments " | ||||||
| [29] | xml-rest | ::= |
| ||||||
| [30] | xml-fragments | ::= | { xml-text | { query-expression } } | ||||||
| [31] | xml-text | ::= | ...see text... |
Between the prefix and the following tag name or attribute name there must not be any whitespace. Text within attribute values may not include the string terminator ". XML text within an element may not include terminators <, >, { or }. If the characters { and } are used as-is, they have to be encoded as B; and D;, respectively.
XML prefixes are completely independent from TMQL prefixes.
The following XML content samples are valid:
<copyright>Copyright Holder</copyright>
<message>Celine Dion has quite a different sound than {$bn}.</message>
<message{$kind} title="{$x}">This may work.</message{$kind}>
<pro:code xmlns:pro="{$uri}" lang="pascal">procedure TEST () B; writeln; D;.</pro:code>
The following XML content samples are invalid:
<copyright>Copyright Holder (no end tag)
<code lang="pascal">procedure TEST () { writeln; D;.</code> (opening { indicates subexpression)
The following XML content may produce errors during evaluation:
<mess{$x}>This is broken.</{$y}age> (XML wellformedness depends on variable binding)
There is no support for XML processing instructions, CDATA segments, DOCTYPE constructs and XML comments.
XML namespaces can be declared with xmlns attributes and will become effective in the result.
Within an XML text stream whitespaces are significant, also those which precede the opening tag and those which follow the closing tag.
In the following FLWR expression the nested <person> element is preceded by a line-break (indicated below by ⌋) and blanks (indicated by ⎵) and is followed by another line-break. For each iteration over person instances these whitespace characters must be added to the result XML fragment:
return⌋
⎵⎵<persons>{⌋
⎵⎵⎵⎵for $p in // person⌋
⎵⎵⎵⎵return⌋
⎵⎵⎵⎵⎵⎵<person>{$p / name}</person>⌋
⎵⎵}</persons> |
There is also a line break before the <persons> opening tag which will be part of the overall result fragment.
When XML content is evaluated, first all nested query expressions are evaluated in the current context in no particular order. The content generated by these expressions is then embedded into the XML content, replacing the text of the query expressions (including the {} bracket pair) according to the following embedding rules:
Atomic content is converted into its string representation.
String content is used as-is except that the special characters &, ", <, >, ' are automatically encoded into the predefined entities &, ", <, >, ', respectively.
XML content is used as-is.
Tuple sequences are converted into their string representation (4.8.5).
TM content is serialized using XTM ([XTM 2.0]) according to following rules:
Topic items are serialized using the <topic> element (XTM, 4.5) whereby all identification information (subject address and subject identifiers), all types, all names and all occurrences are included.
Name and occurrence items are serialized using the <name> (XTM, 4.10) and <occurrence> element (XTM, 4.15), respectively, whereby all type, scope and value information is included.
Association items are serialized using the <association> element (XTM, 4.18) whereby type, the scope and all roles and players are included.
For any topic id or topicRef an appropriate topic item identifier has to be used.
The resulting text is interpreted as XML content which matches the element in [XML 1.0]. It is an error if XML parsing fails.
TM instance data can be generated using CTM [CTM] as syntax:
| [32] | tm-content | ::= | """ ctm-instance """ |
The content is controlled using a CTM text stream, whereby TMQL query expressions can be embedded.
When such TM content is evaluated, a TMDM instance will generated according to the CTM deserialization rules. The result is a tuple sequence of singletons which contain only either a topic item or an association item.
The following example copies over the person topics together with a newly generated association for each person instance:
for $p in // person return """
{$p} # embed the whole topic
is-employed (org: big-corp, employee: {$p})
""" |
Embedded query expressions must be wrapped inside a {} bracket and are only allowed at following particular positions within the CTM text stream:
Wherever a topic (CTM, 3.6 Topics), or association (CTM, 3.8 Associations) declaration is expected:
The result of the query expression must be a tuple sequence consisting of singleton tuples, otherwise an error is flagged. These singletons will be injected into the TMDM instance as follows:
every topic item will be injected together with all identity information (subject identifiers and subject locators), all names and all occurrences (any reification thereof are ignored),
every association item will be injected together with its type, the scope and all role items,
every string which follows the CTM iri-ref syntax (CTM, 3.3.5 IRI References) will be interpreted as subject identifier; a topic item will be injected using this very subject identifier,
every string with CTM identifier syntax (CTM, 3.3.6 Topic Identity) will be interpreted as item identifier and a topic item with such item identifier will be injected,
every other content is ignored.
for $p in // person
return """
{$p} # embed the whole topic
{$p <- member} # embed every association
{$p ~ } # create topics with these subject identifiers
{'mr-x'} # create topic with this item identifier
""" |
Wherever a topic assignment (CTM, 3.7 Topic Tail) is expected:
The result of the embedded query expression must be a tuple sequence consisting of singleton tuples, otherwise an error is flagged. These singletons will be injected into the TMDM instance as follows:
every name or occurrence item will be attached to the current topic,
every other content is ignored.
for $p in // person
return """
? # use CTM wildcard
{$p / name} # add all names
{$p / homepage} # add all homepage occurrences
""" |
Wherever a CTM literal (CTM, 3.4 Literals) within an assignment (name or occurrence) is expected:
The result of the embedded query expression must be a tuple sequence consisting of singleton tuples, otherwise an error is flagged. Every such tuple value is stringified according to 4.8.5.
When inside a name assignment, for every tuple value a name item is generated for the current topic, whereby the tuple value is used as string name.
When inside an occurrence assignment, for every tuple value an occurrence item is generated for the current topic, where the tuple value is reinterpreted as CTM literal.
This guarantees that any data type information for literals is properly carried over into the generated instance.
for $p in // person
return """
? # use CTM wildcard
name: {$p / homepage } # add (possibly a number of) names
""" |
Inside a CTM string (CTM, 3.4 Literals):
The tuple sequence resulting from evaluating the query expression is stringified according to 4.8.5. That string is interpolated into the CTM string.
for $p in // person
return """
? use CTM wildcard
homepage: http://{ $p / domain }/{ $p! }.html
""" |
Whereever a topic identifier (CTM, 3.3.6 Topic Identity) is expected:
The result of the query expression must be a single singleton tuple, otherwise an error is flagged. If the value is a string that can be interpreted as item identifier, then that is used. If the value is a topic item, then an item identifier is used. Otherwise an error is flagged.
for $p in // person return """
is-employed (org: big-corp, employee: {$p})
""" |
Value expressions are expressions which produce either a single value or a sequence of values:
| [33] | value-expression | ::= |
| ||||||||||||
| [34] | infix-operator | ::= | ...any in the predefined environment... | ||||||||||||
| [35] | prefix-operator | ::= | ...any in the predefined environment... |
Value expressions are either directly producing content, using the invocation of a function, or a combination of other value expressions using binary infix or unary prefix operators. The accepted operators, their symbols and their precedence are defined in Annex A. Operators with higher precedence bind stronger. Operators with the same precendence level are interpreted to bind left-to-right.
Every function there marked as infix can be used as infix operator, every function marked as prefix can be used as prefix operator. All operators are eventually mapped into their function equivalent so that a value expression either generates content directly or computes it via a function application.
The following are valid value expressions:
1 + 2 $person / age $person / age >= 18 |
| [36] | function-invocation | ::= | item-reference parameters | ||||||
| [37] | parameters | ::= |
|
A function is addressed via an topic item reference which will be resolved in the effective map (6.2). It is an error if no such function topic exists there.
Every function must have formal parameters to which the actual parameters will be assigned. The formal parameters can be positional; in this case their names are $0, $1, etc. Or they can be explicitly named.
For a positional parameter association first the tuple expression is evaluated in the current context rendering a tuple sequence. One by one, a tuple from this sequence is taken. The individual values of that tuple are then assigned to the formal parameters $0, $1, etc. It is an error if there are more or less formal parameters than values in the tuple. Then the function is invoked.
The individual function results for every tuple form the result tuple sequence. Any ordering of the parameter tuple sequence will be maintained in the result.
A function math:sqrt would be invoked like this:
math:sqrt ($p / age) |
Should a person have several age occurrences, then the result list will contain the square root for each of these.
For a named parameter association, first all value expressions are evaluated in the current context. Also here the result is effectively a tuple sequence except that the components of individual tuples are not addressable via an index, but a name.
For each such (named) tuple the function is invoked, whereby actual and formal parameters are associated via their name. While it is allowed that a formal parameter in the function does not get associated a value, it is an error if an actual parameter does not have a corresponding formal parameter.
A function nr-accounts would be called as:
nr-accounts (owner: "James Bond") |
In addition to the formal parameters, the effective map is passed as a special parameter to the function. How this is accomplished is not specified by this International Standard.
How the formal parameters are made known to the TMQL processor is not specified by this International Standard.
WHERE clauses (6.4, 6.5) and filters (6.6.2) make use of boolean expressions:
Boolean expressions can be combined with the binary boolean operators & (AND) and | (OR). Boolean primitives can be negated. The brackets () can be used to override the usual precedence, namely that & binds stronger than | and that not binds stronger than &. FORALL clauses test whether a certain condition — again a boolean expression — is satisfied for all members of a particular sequence of values. In contrast, an EXISTS clause tests whether a condition is satisfied by at least one of the sequence members.
As usual the operators & and | bind the immediate boolean expressions. In the example
every $opera in // opera satisfies composed-by ($person: composer, $opera: opera) & some ... |
the & binds the composed-by and the subsequent SOME clause as the nesting suggests and not as the following indentation insinuates:
every $opera in // opera satisfies composed-by ($person: composer, $opera: opera) & some ... |
As the language is ontological neutral, the constants true and false have no semantic meaning. They are just values, like all integers or all strings. Any (simple or complex) value can play the role of the traditional true, and the empty tuple sequence plays the role of false.
Before a boolean expression is evaluated all free occurrences of the variable $_ are existentially quantified according to 5.3. Then the boolean expression is evaluated in the current context.
If other free variables exist in the boolean expression and these variables are not bound to a value in the current context, then the evaluation will result in an error. Such variables are not implicitly existentially quantified. This is meant as a safeguard to avoid that accidentially potentially large maps are traversed.
If the evaluation of a boolean primitive renders a non-empty tuple sequence, then negating it with not will return the empty tuple sequence. Otherwise, an arbitrary non-empty tuple sequence will be generated.
The operator & represents the logical AND; and | the logical OR.
Additional Notation: To test whether the value of $0 (the first component of the current tuple) is of a particular type or in a particular scope, the following shorthands are provided:
| [Q] | boolean-primitive | ::= | ^ anchor |
| ==> | . >> types == anchor | ||
| [R] | boolean-primitive | ::= | @ anchor |
| ==> | . @ == anchor | ||
To select only the english names from persons, one can use:
// person / name [ @ english ] |
An EXISTS clause allows to test whether a particular condition can be satisfied by either a minimal or a maximal number of tuples out of a given sequence:
| [40] | exists-clause | ::= | exists-quantifier binding-set satisfies boolean-expression | |||||||||
| [41] | exists-quantifier | ::= |
|
If the EXISTS clause is introduced with the token some, then it is an numerically unrestricted EXISTS clause. Otherwise it is called numerically restricted. For the latter, the integer must be positive. In the case of at least that integer is a lower bound, for at most it is an upper bound.
The following boolean expression tests whether in the current context map an opera exists with a libretto written by Pink Floyd (maybe together with others).
some $opera in // opera satisfies
$opera <- play -> libretto <- opus -> author == pink-floyd |
First a sequence of binding sets is generated (5.4). Then each of these sets will be pushed onto the current context for the evaluation of the inner boolean expression.
A numerically unrestricted EXISTS clause evaluates exactly then to a non-empty tuple sequence if there exists at least one such binding set for which the evaluation of the boolean expression returns a non-empty sequence. If there is no such binding (or no binding at all), the overall result is the empty sequence.
The following expressions all return the empty sequence:
some $a in null satisfies exists 1 # there is no value in null |
some satisfies exists 1 # not a single binding |
some $a in %_ satisfies null # many values, none satisfies |
Additional Notation: If the boolean condition itself is not relevant, the following abridged form can be used:
| [S] | exists-clause | ::= | exists content |
| ==> | some $_ in content satisfies not null | ||
The following boolean expression tests whether a given person topic (bound to $person) is an author, i.e. is involved in an association via the role author:
exists $person <- author |
Additional Notation: The syntax allows the exists token to be omitted:
| [T] | exists-clause | ::= | content |
| ==> | exists content | ||
That the exists token can be omitted, allows to write more intuitively
select $p / name where $p isa person |
instead of the more canonical
select $p / name where exists $p isa person |
A numerically restricted EXISTS clause with a lower bound N evaluates exactly then to a non-empty tuple sequence if there exists at least N (including N, or more) such binding sets for which the evaluation of the boolean expression returns a non-empty sequence. If there is no such binding set (or no binding set at all), the overall result is the empty sequence.
The following tests whether there are at least 3 persons over nineteen
at least 3 $p in // person
satisfies
$p / age >= 19 |
A numerically restricted EXISTS clause with an upper bound N evaluates exactly then to a non-empty tuple sequence if there exists at most N (including N, or less) such binding sets for which the evaluation of the boolean expression returns a non-empty sequence. If there is no such binding set (or no binding set at all), the overall result is a non-empty sequence.
A FORALL clause can be used to test whether all tuples from a given tuple sequence satisfy a certain condition:
| [42] | forall-clause | ::= | every binding-set satisfies boolean-expression |
Like for EXISTS clauses, a sequence of bindings is generated first. Each of these bindings is pushed onto the context for the evaluation of the inner boolean expression.
A FORALL clause is evaluated in the current context. It evaluates to a non-empty tuple sequence if for all generated binding sets the boolean expression evaluates to a non-empty tuple sequence. Otherwise it will evaluate to the empty sequence. It also evaluates to a non-empty sequence if not a single binding set could be generated in the first place.
The following expression tests whether all politicians are honorable persons.
every $p in // politician satisfies
$p <- person -> trait == honorable |
If our universe would not contain a single politician, then this boolean expression would evaluate to a non-empty sequence. Otherwise, only when each of them has (at least one) trait with value honorable, only then the expression evaluates to a non-empty sequence.
The following boolean expression encodes the statement everybody loves everyone (else):
every $p in // person,
$p' in // person satisfies
loves (lover: $p, loved: $p') |
TMQL is assuming a closed world, so there is a semantic relationship between EXISTS and FORALL clauses:
| [U] | boolean-expression | ::= | every binding-set satisfies boolean-expression |
| ==> | not some binding-set satisfies not ( boolean-expression ) | ||
During the evaluation variables are used to bind values. A variable identifier must be prefixed by a sigil (either a $, @ or %) and can be postfixed by any number of primes ('):
| [43] | variable | ::= | /[$@%][\w#]+'*/ |
The sigil signals whether the variable can be bound to either a simple value (atom or Topic Map item), a tuple or a tuple sequence. This is directly followed by the variable name, consisting of alphanumeric characters (including the underscore _ and hash #). Any number of trailing primes may be attached.
Valid variables are $a, $a', $_, @a_long_list_name or $23. Examples for invalid variables are x (sigil missing), $a-string-world (dashes not allowed in names) or @list ' (no blanks before the prime are allowed).
Following special variables are assigned automatically during an evaluation. They cannot be redefined in variable assignments (5.4).
The environment map (6.3) contains all necessary background knowledge for a TMQL processor. This includes all data types and their related functions from the predefined environment (Annex A).
It is initially adopted from the querying application (explicitly or implicitly). Query expressions can locally enrich this environment.
Whenever a map has been referred to (as in a FROM clause within a SELECT query expression, via a path expression, or via a reification navigation step), it becomes the current context map. All item references and navigation steps are interpreted relative to this map.
Whenever within a path expression a tuple sequence is iterated over in projections or filters, the tuples in the sequence — one by one — become the current tuple.
Whenever a tuple sequence is iterated over, this variable contains the current position of the tuple in the sequence (counting from 0). Its value is always the same as the result of the function fn:position() (Annex A).
Whenever one particular tuple (sequence) is considered, $0 projects the first column from it, $1 projects the second, $2 the third and so forth.
This write-only variable can be used as placeholder inside a boolean expression if the value it binds to is not of interest to the result.
Additional Notation: As it appears quite frequently that the first (and often only) component of a tuple is to be addressed, we introduce a shorthand:
| [V] | variable | ::= | . |
| ==> | $0 | ||
To select only persons who are older than 18, one can use // person [. / age > 18 ].
Variables always exist in a lexical scope, i.e. a lexical part of a query expression. For variables which are assigned within a variable assignment (5.4) this scope starts directly after the assignment and reaches until the syntactic end of the directly enclosing query expression. For all other variables the scope is the whole query expression. These global variables cannot be rebound to other values within the query expression; they are effectively treated as constants.
A variable binding connects one particular variable with a value. A binding set is a set of such bindings, with the constraint that one particular variable may only appear once.
The set { $a => 23, $b => "text" } is a binding set.
Once a variable is bound to a particular value, this binding cannot be changed. The same variable can get a different value in another binding, though, hiding the former binding (immutability of variables).
During the course of the nested evaluation of a query expression a processor will maintain stack of binding sets, the variable context (short: context). Whenever variables are introduced, a corresponding set of bindings will be generated. These binding sets will be pushed onto the context for the duration of the evaluation of the inner expressions. At the end of that evaluation the last binding set is popped from the context.
A processor will always maintain the following constraints on contexts:
The value of a particular variable in the context is determined by a binding for that variable in that binding set which has been pushed last onto the context.
If the variable names differ only in the number of primes, then their values MUST differ.
Any two different variables may be bound to different or the same values.
$a, $a' and $a'' within the same context can never have the same value assigned. So to find three different neighbors, the following will work:
...
where
is-neighbor-of (tm:subject : $a, tm:subject : $a')
& is-neighbor-of (tm:subject : $a', tm:subject : $a'')
& is-neighbor-of (tm:subject : $a'', tm:subject : $a) |
If duplicates are acceptable in the result, then choosing completely different variables makes them independent:
...
where
is-neighbor-of (tm:subject : $a, tm:subject : $b)
& is-neighbor-of (tm:subject : $a, tm:subject : $c)
& is-neighbor-of (tm:subject : $b, tm:subject : $c) |
Like any other variable the anonymous variable $_ can be used as placeholder to bind to any value. It is, however, write-only in that its value can never be