value
production.items
, whose children are all elements named
item
. Since the name of the child element is always the
same, it is redundant, so I chose to leave it out: the content of the
items
attribute is now a list of anonymous elements.items
attribute to contain a single item rather than a list if only one item has
been ordered, but the program reading the data would then have to cater for
both possibilities.)DoubleLiteral
of XPath 2.0 (it requires digits both before and
after the decimal point), and the equivalent in JSON (it allows leading zeros). The
value space is not binary floating point, but decimal. Specifically, it is the set
of values that can be represented in the form N * 10^M
where
N
and M
are integers, and N
is not a
multiple of ten. Implementations may impose limits on this infinite set.\n
, \r
, \t
,
\s
, and \S
represent newline, carriage
return, tab, space, and non-breaking space respectively.\
followed by a sequence of
whitespace characters represents nothing. This means that a FtanML
editor can reformat the text for display purposes by inserting or
removing escaped newlines without changing the actual content.\\
for backslash,
\"
for quotation mark, \'
for apostrophe,
\|
for vertical bar, \`
for backtick,
\<
for a left angle bracket, \[
for a
left square bracket, \{
for a left curly brace.\xHHHHH;
represents the
Unicode codepoint whose hexadecimal value is HHHHH
. This
may be any number of digits, followed by a semicolon. (Unlike JSON,
non-BMP characters are represented by the actual codepoint, not by a
surrogate pair.)\[§....§]
where §
is any character
that does not appear in the string. This is analogous to XML's CDATA
section, except that it can also be used in attributes: it allows a
literal string to appear without escaping of special characters. For
example a sequence of four backslashes might be written
\[⟡\\\\⟡]
. Cells are handy for things such as regular
expressions and Windows filenames, and for authoring papers that
describe new markup languages.\
,
{
, and the character used as the string delimiter ("
or '
). We'll come on to the significance of curly braces
later.[1, 3, "London", null]
[1 2 3]
is equivalent to
[1,2,3]
and
[<first><last>]
is
equivalent to [<first>,<last>]
.
null
is implicit if there is nothing between
two commas, or before the first comma, or after the last. So
[,,]
is equivalent to
[null,null,null]
Example | Explanation |
---|---|
<> | An empty element (no name, attributes, or content) |
<br> | An empty element named br |
<age 23> | An element whose name is age and whose content
value is the number 23 |
<colors ["red", "green", "blue"]> | An element whose name is colors and whose
content value is a list of three strings |
<x=0.13 y=0.57> | An unnamed element containing two attributes, both numeric |
<polygon coords=[[1,1], [1,3], [3,2]]> | An element named polygon with an attribute named
coords whose content value is a list; the list
contains three sublists, and each sublist contains two
numbers. |
<[<i><j><k>]> | An unnamed element whose content value is a list of three elements. Note the omission of the optional commas. |
<`Graduate Trainee` `date of birth`="1995-01-01"> | An element where both the element name and attribute name contain spaces. |
|H<sub|2|>O|
represents text consisting of the
character "H", an element whose name is sub
and whose content is the
rich text "2", and the character "O".org_w3c_xsl_transform
.status
or
name
are perfectly adequate and cause no ambiguity.[<`{http://www.w3.org/1999/XSL/Transform}stylesheet` version="2.0"...>
<`xsl:stylesheet` `xmlns:xsl`="http://www.w3.org/1999/XSL/Transform" version="2.0"...>
.
The FtanML system will not attach any meaning to such namespace declaration
attributes, but it is capable of representing them if required.
Note that any name containing a colon (or various other characters such as ".") needs to be
backtick-quoted.
[1,2,3]
are
"the same list", and this is also true for elements.x00
) is
permitted. The way in which such conflicts are resolved is outside the scope of this
paper.null
,
boolean
, number
, string
, list
,
element
, and text
are always available; other types are
user-defined.<number gt=0 le=1000>
, where number
is the
name of a base type, and attributes such as gt=0
and le=1000
define constraints. These attributes are referred to as gt
facet defines a minimum value (exclusive), while the le
facet defines a
maximum value (inclusive). Specifying a base type is often unnecessary — in this example
every value that can be greater than zero is necessarily a number, so every value that
satisfies the predicate will also satisfy the base type. However, including the base
type can still be useful to aid clarity.org_ftanml_calendar_dateType
) are imported from an external type library.form
attribute. The value of this attribute is a proforma element. The name
of the proforma element matches the name of the instance element; the
attributes of the proforma element define the types of the attributes of
the instance element; and the content value of the proforma element defines
the type of the content value of the instance element.<nullable<T>>
. This reflects the fact that
an absent attribute is equivalent to an attribute that has the explicit
value of null; so as well as the normal type of the attribute, the
schema must also allow it to take the value null.SKUType
. This helps to avoid clutter in a string that makes generous use
of special characters, especially backslashes.<value>
represents a type that matches
every value.T
, U
, V
, the construct
<anyOf [T, U, V]>
represents the union of these types,
while <allOf [T, U, V]>
represents their intersection.<anyOf [<number>, <string>]>
allows numbers
and strings, while <allOf [<positive>, <even>]>
allows
values provided they satisfy both the (user-defined) types positive
and
even
.<nullable <T>>
is equivalent
to <anyOf [<T>, <null>]>
: that is, either T
or
null. Thus <nullable <number>>
matches either a number, or
null.<enum=[A,B,C,...]>
. For example,
<enum=["red", "green", "blue"]>
matches the three
specified strings and nothing else. A singleton enumeration can be defined with the
eq
facet: for example <eq="">
matches the
zero-length string only.<not <T>>
denotes a type that matches all
values that are not instances of T
. This can be useful in constructing
more complex types; for example <not<eq="">>
matches all
non-empty strings, while <allOf [<number>, <not <eq=0>>]>
matches values that are numbers and that are not equal to zero.<assert={$.startsWith("abc")}>
. To understand
assertions, however, we need to look at the scripting language, which comes later in
the paper. (The curly braces signal that the value is a function; this represents an
extension to the base FtanML syntax which is used only in scripts.)ge
,
gt
, le
, and lt
, corresponding to the XML
Schema facets minInclusive
, minExclusive
,
maxInclusive
, and maxExclusive
, together with the
facets eq
and ne
which are applicable to all values. For
example, the type consisting of numbers in the range 0 to 100 inclusive may defined
as <number ge=0 le=100>
. (As mentioned earlier, the element
name number
is redundant, because only a number can satisfy the other
constraints.) step
facet constrains the number to be an integer multiple of the
given increment. The most common values (both found in our example schema) are 1,
which requires the value to be an integer, and 0.01, which is often suitable for
currency amounts. Specifying step=17.2
would be unusual, but is
perfectly legal. The facet does not constrain the way the value is written, for
example an integer can be validly written as 1.00000
.<string pattern="[A-Z]*">
. There are no special facets
for defining a minimum, fixed, or maximum length, since regular expressions are
sufficient for this purpose.<list grammar=....>
. A simple grammar might allow a list
to consist of a sequence of zero or more numbers. This would be defined like
this:grammar
facet is an element representing
the root particle in this tree.seq
; an optional
occurs
attribute; and content which is a list containing
the child particles in the tree. For example:
<seq occurs=[0,] [<white>,<black>]>
, which
matches an alternating sequence of values of types <white>
and <black>
.choice
; an optional
occurs
attribute; and content which is a list containing
the child particles in the tree. For example:
<choice occurs=[0,] [<white>,<black>]>
, which
matches sequence of values, each of which can be either of
<white>
or <black>
type.occurs
attribute. For example
<number>
, or <number occurs=10>
.
The occurs
attribute defaults to 1; it appears alongside the
attributes defining facets of the type, though it is not really a property
of the type, but rather of the particle referring to the type.occurs
attribute is either an integer (indicating a
fixed number of occurrences), or a list of size two (indicating a range with a
minimum and maximum). The first item must be an integer, the second can be either
another integer, or null to indicate an unbounded range. For example
[0,1]
indicates an optional particle (zero or one occurrences),
[0,]
indicates zero or more, and [1,]
indicates one or
more. The default is occurs=1
.Example | Explanation |
---|---|
<seq [<string>, <number>, <number>]> | A string followed by two numbers |
<seq [<string>, <number occurs=2>]> | A string followed by two numbers |
<occurs=[0,] <seq [<string>, <number>]>> | An alternating sequence of strings and numbers |
<enum=["red", "green", "blue"] occurs=[1,]> | A sequence of one or more strings each taken from a defined set of colour values |
<occurs=[0,100] <choice [<string>, <number>]>> | A list of up to 100 items, each of which may be either a
string or a number. Note that when the sub-particles of a choice
are leaf particles, an alternative approach is to define a union
type using <anyOf> |
form
facet.
Its value is an element, known as a proforma, which works as follows:height
and
width
attributes must be numbers, and whose content value must be
absent (null).height=<nullable<number>>
indicates that the height
attribute must either be a number, or null.
Recall that omitting an attribute is the same as giving it a value of null.elemName
facet defines the type of the element name.<element elemName=<enum=["i", "b", "u"]>>
constrains the element
name to be one of the names listed.attName
facet defines the type that all attribute names must conform to (for
example, as an enumeration, or by means of a pattern). This is the easiest
way of prohibiting attributes from appearing (the other way is to constrain
the value to be null). For example, attName=<ne="xmlns">
would disallow the attribute name xmlns
; this constraint could
also be expressed in the proforma as xmlns=<null>
.content
facet. The value is a type. For
example, content=<boolean>
constrains the content to be the
boolean value true or false, while content=<null>
constrains
it to be null (which can be achieved either by omitting the content, or
using the FtanML keyword null
).items
whose children are
elements named item
with string content. We can define the type of items
like this:elements
facet, whose value is a type.
All elements appearing in the text must conform to this type.headword
element, and after that there are no
constraints.id
must have distinct values. Null
values are excluded. This facet can be applied to lists and elements; in each case
the supplied function is used as a mapping function, and is applied to each item in
the list or each attribute of the element, as if by the !
operator; the
value is invalid if the resulting list contains any duplicates. So a simple
constraint that all the numbers in a list of numbers be unique can be expressed as
unique={$}
; a constraint that the names of the attributes in an
element should each start with a different letter can be written
unique={substring($, 0, 1)}
, and a constraint that all the non-null
attributes of an element should have distinct values can be expressed as
unique={$2}
(when a mapping function is applied to an element, the
first argument $
is the attribute name, and the second argument
$2
is the attribute value).<ref=<from={$@ref} to={$@id}>>
from
function
(again excluding any nulls) must be a subset of the set of values selected by the
to
function.{$1 +
$2}
$1
, $2
etc, where
$N
refers to the Nth supplied argument in the function call.
The expression $
can be used in place of $1
to refer
to the first argument, and is particularly useful for functions that expect a
single argument. It can be used in rather the same way as .
(the
context item) in XPath, and plays a similar role to _
in languages
such as Perl or Scala.{name($) != null}
.{$1 +
$2}
expects two arguments, but it can be called with more than two
arguments (excess arguments are ignored), or with fewer than two (unsupplied
arguments default to null).$$
returns all the supplied arguments in the form
of a list. This makes it possible to write functions that take a variable number
of arguments: the actual number is accessible as count($$)
. self
is bound to the
function itself. This makes it easy to write anonymous recursive functions: for
example a function to compute the sum of its arguments can be written as
{if empty($$) then 0 else $ + self(tail($$))}
. We'll see later
how to write mutually-recursive functions.f(<x=2 y=3>)
; and in the function body the supplied values
can then be referenced as $@x
or $@y
.indexOf
function starts by giving names to its arguments and checking their type,
which simultaneously makes the function more robust and more readable.
F
is an expression that evaluates to a function, then the
function may be called with arguments x
and y
using
the expression F(x, y)
.f
is a variable whose value is a function, and if the function
has at least one argument, then a function call can be written either as
f(x,y)
or as x.f(y)
. ?
for one of the arguments: contains(?, ':')
returns a function whose effect is to test whether its first argument is a
string that contains a colon.+
operator corresponds to the plus
function; a + b
has the same meaning as plus(a, b)
or
a.plus(b)
. All the operators in the language, including higher-order operators, are defined
in terms of functions, to allow them to be passed as arguments to higher-order
functions.{[$, $+1, $+2]}(5)
returns the list [5,
6, 7].[1 to 5,
10]
produces the length-2 list [[1,2,3,4,5],10]
rather than
the length-6 list [1,2,3,4,5,10]
. The latter result can be achieved
either by applying the flatten()
function explicitly, or by using list
concatenation/append operators: for example (1..5).append(10)
.<img size=(x+1) caption="Figure
{n}">
. The same applies to the content value. Note that curly braces are
used only for inline expansion of strings and text (and for writing functions); to
compute general structured content, parenthesized expressions should be used. The
expression:<size x=(a+1) y=(if a=2 then 3 else null)>
might
produce the output <size x=3>
.a=3
or a="blue"
or
a=false
.a="Chapter {n}"
The value of the attribute is obtained by
evaluating the embedded expressions as strings and inserting the resulting
strings into the text.a=[n, n+1, n+2]
.a=<x=(n+1) y=(n+2)>
a=(n+1)
a={$+1}
. In this case the value of
the attribute is the function, not the result of evaluating the
function.element("img")
constructs an element with a
given name, and the add()
function adds an attribute with a given name
and value (copying the element to create a new element). The last call adds the
element content, represented as an attribute with an empty name. It should be
remembered that although we use the term "element", FtanML elements will not only be
used in the way that XML elements are traditionally used, but also in the way that
maps are used in other programming languages, where the keys (attribute names) are
highly dynamic: indeed, to satisfy the kind of use cases for which maps are being
added to XSLT 3.0.toText()
function.orElse
allows a default to be substituted when a value
is null. For example a.orElse(0)
returns the value of a
unless it is null, in which case it returns zero. This function could be defined as
{if $1=null then $2 else $1}
.true
, false
, and null
are reserved: they
are used syntactically like variables, but have fixed predefined values. Language
keywords such as if
and let
are also reserved: unlike
XPath, this is possible because bare names are not used to refer to elements and
attributes in input documents.let x=2; x+x
returns
4.eq
function (operator =
) is defined over all
values. To be equal, two values must have the same fundamental type (this means, for
example, that the string "John"
is not equal to the rich text
|John|
). Strings are compared codepoint-by-codepoint. Lists are
equal if they have the same size and their items are pairwise equal. Elements are
equal if they have the same name, and if there is a one-to-one correspondence of
attributes in which both the attribute names and the corresponding values are equal
(the content value is treated here as an attribute). Two texts are equal if the two
sequences of strings and elements making up the texts are pairwise equal.let a=b; a=b
is always true, but formalizing this is not easy
without introducing some notion of identity.ne
function (operator !=
) is the inverse of
eq
.le
, lt
,
ge
, gt
, and their corresponding operators
<=
, <
, >=
,
>=
) is defined over numbers and strings only. Strings are
sorted in Unicode codepoint sequence.V
is present in a list A
(the equivalent of the =
operator in XPath) is sufficiently common that
we provide a function, in(V, A)
with corresponding operator ∊ (x220A).
The function in(V, A)
can be defined as {let V=$1; let A=$2;
exists(A?{$=V})}
. (This uses a filter operator which we will introduce in
due course.)collation(<lang="se">)
. This returns an element, whose
attributes are functions. One of these functions is a sort function, so to sort a
list of strings using Swedish collation, one can write
collation(<lang="se">)@sort(input)
. Other functions available as
attributes of a collation include comparison functions eq
,
le
, etc, and collation-sensitive versions of other functions that
involve string comparison such as in
, min
,
max
, indexOf
, contains
,
startsWith
, endsWith
, substringAfter
,
substringBefore
.A = null
returns true if A
is null,
false otherwise. (This is not as obvious as it might seem, given the semantics in some
other languages.)isA
tests whether a value belongs to a given type.
Types are represented using the element representation introduced in an earlier
section. For example, x.isA(<number ge=0>)
returns true if the
value of x
is a number and satisfies the facet ge=0
.
Recall that a type is a predicate, not a label associated with a value, so this
tests whether the value meets all the constraints defined by the type, not
whether the value carries any particular type label.isNull
, isBoolean
,
isNumber
, isString
, isList
,
isElement
, isText
, and isFunction
are
available to test for membership of the primitive types.as
is similar to isA
, but instead of
returning a boolean indicating whether or not the value is a member of the type,
it returns the value unchanged if this is the case, and throws an error
otherwise. We saw this function used to check the arguments to a function call.asNull
, asBoolean
,
asNumber
, asString
, asList
,
asElement
, asText
, and asFunction
are
available to test for membership of the primitive types. In each case, they return
the argument unchanged if it matches the corresponding type, or throw an error
otherwise.toString
,
toList
, and toText
. There are no general rules here;
as in other languages, the rules for what can be converted to what are inherently
ad-hoc. parse
function takes a FtanML lexical representation of a value
and returns the corresponding value; conversely, serialize
takes a
value and returns its FtanML lexical representation. and
, or
, and not
are
available. The first two have equivalent operators &&
and ||
.(null||true)
is true
.plus
, minus
, times
,
div
, idiv
, and mod
are defined; the first
four have corresponding operators +
, -
, *
,
and /
. Arithmetic is performed in decimal. Division by zero is an
error; the precision of the result of division is implementation-defined, as are
limits on the value space.abs
, round
, floor
,
ceiling
have the same effect as in XPath.parse
may be used to convert a string to a number.
Writing parse(X).asA(<number>)
checks that the value was
indeed numeric.sum
, avg
, min
,
max
work as in XPath.to
or its equivalent operator ..
returns a list of consecutive integers, for example 1..5
returns the
list [1,2,3,4,5]
.toString
function can be applied to any value without
exception to convert it to a string. If the input is already a string, it is
returned unchanged. If the input is a boolean, number, or null, the result is the
same as the FtanML literal representation of the value. If the input is a list, the
result is string-join($!toString, " ")
, that is, the space-separated
concatenation of the string values of the members of the array. If the input is an
element, the result is string(content($))
, that is, the string value of
the element's content. If the input is rich text, the result is
string-join($.toList()!toString)
, that is, the concatenation
(without space separation) of the string-values of the strings and elements making
up the text. If the value is a function, the resulting string begins with "{" and
ends with "}", and is otherwise implementation-defined; it is not necessarily a
string that can be parsed and executed as a function."See section {s} in chapter {n}"
. This mechanism can be used
wherever a string literal is permitted. The result of the enclosed expressions is
converted to a string if necessary, by using the toString
function.A
is obtained using
count(A)
(or equivalently, A.count()
).A[n]
selects the n'th item in list A
.
This construct is never used for filtering, only for subscripting. If n
is out of range, the expression returns null. This operation is also available as a
function itemAt(A, n)
.cat(a, a)
(operator ++
) concatenates
two lists. The function append(a, i)
(operator +~
) appends
an item to a list, while prepend(i, a)
(operator ~+
)
prepends. Thus for example 0 ~+ [1,2,3] ++ [4,5,6] +~ 7
returns the
list [0, 1, 2, 3, 4, 5, 6, 7]
. head(a)
is equivalent to a[0]
, while
tail(a)
equates to remove(a, 0)
.flatten
flattens a list: it creates a new list in
which any non-list items in the argument list are copied to the new list, and any
list items are processed by copying their contents. This only works one level deep.
So flatten([[1,2],[3,[4,5]]])
returns
[1,2,3,[4,5]]
.index-of
, remove
, subsequence
work as in XPath, except that indexing starts at zero. The
insert-before
function inserts a single item at a specified
position; if the supplied item is a list, it becomes a nested list (there is no
flattening).toList
works as follows: if the argument is a list,
it is returned unchanged. If the argument is rich text, it is converted to a list
whose members are (non-zero-length) strings and elements. In other cases, the
function creates and returns a singleton list in which the argument is the only
item. This function is useful because it makes it easier to process different types
of content in the same way: a single element looks the same as a list of elements of
length one, which looks the same as mixed content comprising a single element; a
single string looks the same as mixed content containing no elements.forEach
, or the equivalent operator !
,
applies a function to every item in a list. So forEach([1,2,3], {$+1})
returns [2,3,4]
; this can also be written [1,2,3] ! {$+1}
.
Similarly, [1,2,3]!toString
returns ["1", "2", "3"]
. Note
that this is a non-flattening mapping operation; the result list will contain
exactly the same number of items as the input.(1..5)!{<br>}
returns
[<br>, <br>, <br>, <br>, <br>]
select
, or the equivalent operator ?
,
applies a function to every item in a list and returns a list containing those items
for which the function returns true. So select([1,2,3], {$>=2})
returns
[2,3]
; this can also be written [1,2,3]?{$>=2}
.
Similarly, [1,2,"London"]?isNumber
returns [1,2]
.select
operator or function is always a list,
even if only one item is selected. If it is known that the predicate will select
exactly one item, it is necessary to extract that item from the result, typically by
a call on head
, or by using the subscript operation
(A?P)[0]
. Because this is a common operation, the operator
??
is provided, equivalent to ?
followed by
head()
: it selects the first item found, or null if nothing was
matched. A query to find a singleton can now be written, for example
items??{$@id='xyz'}
. all
and some
can be used in
conjunction with the forEach
(!
) operator to perform
universal and existential quantification: they test whether a list consists entirely
of boolean true values (all), or contains at least one true value (some). So, for
example all([1,2,3]!{$>0})
returns true, while
some([1,2,3]!{$=0})
returns false.fold-left
and fold-right
are available as
in XPath 3.0.name(E)
returns the name of an element
E
, or null if it is unnamed. The syntax E.name()
can also be used, of course.!
and ?
) apply to
elements as well as to lists. In this case they expect a two argument function to be
supplied, and call this with the attribute name as the first argument and the
attribute value as the second. The mapping operator returns a list with as many
members as there are non-null attributes in the input; the filter operator returns
an element with the same name as original, and with a subset of its attributes.
These operators treat the content value as just another attribute. They provide the
most general and powerful means of processing elements, and other operations can be
defined in terms of these two. For example, the function content(E), which returns
the content of an element, could be defined as E?{$1=""}!{$2}.E?{$.in("id", "code", "status"}
returns a copy of element
E, retaining only the three specified attributes.E
is an element and xyz
is the name of an attribute
(known at the time the program is written), then E@xyz
returns the
value of the attribute. It returns the value only, not an "attribute node" as in
XPath; if the attribute is not present, it returns null. If the name needs to be
dynamically computed, this can be achieved using an expression in parentheses, for
example E@(X@name)
returns the attribute of E
whose name
is given by the expression (X@name)
. The construct E^
is
an abbreviation for E@``
— it returns the value of the attribute whose
name is the zero-length string, that is, the content value.L?{name($)=N}
achieves this but is a
little cumbersome, and becomes more so if the list can also include values other
than elements. So we provide the construct :N
, where N
is
a name, which represents a function that returns true when its first argument is an
element with the name N
, and false otherwise. So given a list of
elements L
, we can now select those having the name N by writing
L?:N
. If we know there will be only one such element, we can select
it using L??:N
.PO
is the purchase order presented in section 2.1, then
PO@shipTo^??:name
gives the value "Alice Smith"
, while
PO@items[0]@partNum
gives "872-AA"
. PO@items?{$@USprice > 20.00}
.@
operator performs implicit mapping. Specifically: if the
left-hand operand is a list L
, then any lists contained in this list
are expanded recursively. Any values in the expanded list that are not elements are
ignored, so we end up with a list of elements; call this LL
. The value
of the expression L@name
is then defined as LL!{$@name}
.
Note that the result may be a list of lists; it is not flattened.PO@items@partNum
returns the list of strings ["872-AA",
"926-AA"]
.//
represents the deepContent()
function, which is the flattened transitive closure of the content()
function. Specifically, if the result of content()
is a list, then any
element in that list has its own descendants inserted immediately after it. So the
function descendants(E)
can be defined as content(E)!{$ ~+ (if
$.isA(<element>) then $.descendants() else [])}
. So if E
is an element, then E//?:status
will select all descendant elements
named status
, and E//?[$@id=12]
will select all descendant
elements having the id
attribute equal to 12.@@
similarly gives the transitive closure of
attributes()
.forEach
and the equivalent
operator !
are overloaded for elements to process all the attributes of
an element (including the content). The second operand is a function which is called
with two arguments, the attribute name and the attribute value. For example, given
an element E the expression E!{$}
returns the names of its
attributes.select
and the equivalent operator
?
are overloaded to process all the attributes, and return an
element in which only a subset of the original attributes are present. element(name)
constructs an element with a given
name (which may be null). It can also be called with two arguments:
element(name, attributes)
. The second argument is a list of
attributes, each attribute being represented by a two-member list containing the
name and the value.add(element, name, value)
takes an existing element,
and adds an attribute with the given name and value, replacing any existing
attribute with the same value. A new element is returned. Calls to this function can
conveniently be chained: element("rect").add("id", "a001").add("color",
"black")
.addContent
is defined as
add(?, "", ?)
.https://github.com/organizations/FtanML-WG