Bixby Developer Center

Guides

Expression Language Reference

Bixby's Expression Language (EL) is used in conditionals and strings in the *.bxb format.

Strings can include EL functions via the #{%expression%} (or ${%expression%}) notation.

Concept values can be bound to an EL expression by wrapping the expression in the $expr() construct, optionally prefixing it with a type to coerce the value to.

// untyped expression
value: $expr(brand)

// typed expression
value: Distance$expr(calculateDistance($user.currentLocation, point, 'Miles'))

Optional parameters to EL functions are denoted in [brackets].

You can use the various functions listed here in conjunction with the EL Operators, such as ternary conditionals and logic, as well as accessors.

Layout Functions

These functions can only be used in layout files.

  • layout(name): Embed the specified layout (e.g., "address") at that point in the current layout.

  • layoutMode(name, type): Embed a layout like layout(), but override the layout type (e.g. "Summary", "Details", etc.).

Geospatial Functions

These functions provide support for location-based GIS functions. Most of them rely on concepts in the viv.geo Core Capsule.

  • calculateDistance(point1, point2, units): Calculate the distance between two geopoints.

    In this example, calculateDistance() is used in a selection strategy to find points of interest within certain distance ranges:

      selection-strategy {
    id (point_of_interest)
    match {
    PointOfInterest (this)
    }
    named-advice ("within-100-miles") {
    advice ("${calculateDistance($user.currentLocation, this.centroid, 'Miles').magnitude}")
    advise-for { lowerBoundClosed(0) upperBound(100.0) }
    }
    named-advice ("within-1000-miles") {
    advice ("${calculateDistance($user.currentLocation, this.centroid, 'Miles').magnitude}")
    advise-for { lowerBoundClosed(0) upperBound(1000.0) }
    }
    }
  • within(point, shape): Return true if a geopoint is within a certain geospatial region, false if not.

Date/Time Functions

These functions operate on DateTime values. They utilize concepts that must be imported from the viv.time Core Capsule.

  • isFuture(datetime): Return true if the datetime occurs in the future, false if not.

  • isPast(datetime): Return true if a datetime occurs in the past, false if not.

  • isLastDayOfMonth(datetime): Return true if the day of the datetime is the last day of the month. If it is not, or if any of the month, day, or year fields are not set, return false.

  • durationBetween(datetime1, datetime2): Return the duration between two datetimes, as a time.DurationPeriod. Duration is returned in absolute time, regardless of which date is before the other.

  • durationInUnit(datetime1, datetime2, unit): Return the number of specified date/time units present between two datetime nodes. The unit can be any valid chronounit enum.

      durationInUnit(this.departureDateTime.date, now().date, 'DAYS')

    Note that chronunit enums must be all caps, e.g., 'DAYS', 'HOURS', 'SECONDS'.

  • addDuration(datetime, period) and subtractDuration(datetime, period): Add or subtract time duration. Takes a date/date-time and a duration, either as an ISO 8601 duration format, a viv.time.DurationPeriod, or a viv.core.Period.

      // add two days to the given date using ISO 8601 duration format:
    addDuration(dateTime, 'P2D')

    // subtract an instance of viv.core.Period from a given datetime:
    subtractDuration(dateTime, myPeriodType)
  • now([timezone]): Return the current DateTime as a viv.core.BaseDateTime. (The viv.time.DateTime model is an extension of this core structure.) Uses the default user timezone, unless one is provided. Use now().date to return the date only.

Formatting Functions

These functions are intended to be used in template strings to aid in formatting output. This can be especially useful in dialog.

  • concept(node[, form]): Format the supplied node as a concept fragment. You can optionally pass an argument for the form (Definite, Indefinite, Proximal).

  • action(node): Format the supplied node as an action fragment.

  • input(node): Format the supplied node as an input fragment.

  • value(node): Format the supplied node as a value fragment.

  • event(node, dialogNode): Format the supplied node as a specific dialog event mode. In addition to providing the node to render, you also must provide the event mode to render the node as.

  • macro(macro-id[, param1, param2, param3, ...]): Invoke a dialog macro with the specified parameters. For example, suppose this macro has been defined with template-macro-def:

      template-macro-def (Bookmarks) {
    params {
    param (number) {
    type (example.book.Bookmarks)
    max (One) min (Required)
    }
    }
    content: template ("#{value(number)} bookmarks")
    }

    You can invoke that macro in EL with the macro command. This lets you include templates within other templates:

      template ("You have #{macro('Bookmarks', 23)}.")

    The macro ID is specified as a string in the first parameter. Parameters must be specified in the order they are specified in the macro definition; parameters can be omitted at the end if they are Optional or have a default value.

  • list(node, format): Format the individual values of the specified node in the specified format, then join those values as a conjunctive list ("value1, value2, and value3"). The format argument must be one of the fragment template functions: concept, action, value, or event. (Note: this is distinct from the list schema key.)

      if (exists(alarms.repeatDays) && size(alarms.repeatDays) < 7) {
    dialog-template ("#{list(alarms.repeatDays.day, 'value')}")
    } else {
    dialog-template ("")
    }

    If alarms were set on Monday, Wednesday, and Friday, the resulting string from list would be "Monday, Wednesday, and Friday".

  • listWithLimit(node, format, limit): Format up to limit individual values of the specified node in the specified format, then join those values as a conjunctive list ("value1, value2, and value3"). The format argument must be one of the fragment template functions. If limit is exceeded, include the number of omitted values in the formatted list, for example, "John, Cindy, and 3 others".

  • joinAs(format, node1[, node2, node3, ...]): Render the specified nodes in the specified format, then join those formatted strings as a conjunctive list ("value1, value2, and value3"). The format argument must be one of the fragment template functions. (The list and listWithLimit functions operate on values of a single node; joinAs operates on multiple nodes that contain single values.)

  • flattenAs(format, node1[, node2, node3, ...]): Flatten the specified nodes, regardless of cardinality, into a single list, format them in the specified format, then join those formatted strings as a conjunctive list ("value1, value2, and value3"). The format argument must be one of the fragment template functions.

    Note

    The difference between joinAs() and flattenAs() is how they handle nodes with max (Many) cardinality and multiple values. The joinAs() function will render each multi-cardinal node as separate conjunctive lists and then join those in a final list, which can lead to awkward constructions such as "lemons, apples, and pears and butter, milk, and cheese". Using flattenAs in this situation will flatten the lists in each node together before joining them, resulting in the correct construction: "lemons, apples, pears, butter, milk, and cheese".

  • raw(node): Render the single, primitive data value in the specified node as a string, if one exists.

  • integer(value): Format the specified value as an integer in the locale of the current rendering context. A value of 1000.1 would be rendered as "1,000" in the en-US locale.

  • percent(value): Format the specified value as an percentage in the locale of the current rendering context. A value of 1.1 would be rendered as "110%" in the en-US locale.

  • scientific(value): Format the specified value as an scientific number in the locale of the current rendering context. A value of 1000.1 would be rendered as "1.0001E3" in the en-US locale.

  • ordinal(value): Format the specified value as an ordinal number in the locale of the current rendering context. A value of 2 would be rendered as "2nd" in the en-US locale.

  • spell(value): Format the specified value as words in the locale of the current rendering context. A value of 32 would be rendered as "thirty-two" in the en-US locale.

  • number(value[, formatString]): Format the specified value as a number either in the locale of the current rendering context or using the optionally supplied DecimalFormat string. Example: number(airfare.total.price, '#,##0')

  • dateTime(node[, formatString]): Format the specified node as a date/time number either using the default format of the locale of the current rendering context or using the optionally supplied Joda DateTimeFormatter string. Example: dateTime(log.timeStampInfo, "h:mm a")

  • lower(string): Lowercase all characters in the supplied string.

  • upper(string): Uppercase all characters in the supplied string.

  • title(string): Convert the first character of the supplied string to title case.

  • truncate(string, length): Truncate the supplied string to the desired maximum length.

Pagination Functions

These functions are intended for use within page-marker blocks for Hands-Free List Navigation:

...
page-content (page) {
page-marker {
if (isFirstNavPage(page)) {
template ("The first #{size(page)} items are")
} else-if (isLastNavPage(page)){
if (size() == 1) {
template ("The last item is")
} else {
template ("The last #{size(page)} is")
}
} else {
template ("The next #{size(page)} items are")
}
}
}
...
  • isFirstNavPage(node): returns true if the user is on the first page of items, false if not.

  • isLastNavPage(node): returns true if the user is on the last page of items, false if not.

Collections Functions

These functions work on a collection of items where EL functions are supported.

  • contains(node, value): Return true if the specified node contains the specified value, false if it does not.

  • subtract(node1, node2): Remove elements in node1 that are found in node2, then return the resulting subset of node1.

  • intersect(node1, node2): Remove elements in node1 that are not found in node2, then return the resulting subset of node1 (the intersection of the two nodes).

  • getPreviousPage(node): Returns the collection from the previous selection context. This is intended for use in intent blocks.

    In the following example, filterTerm is used to filter a set of businesses, retrieved in the computed-input block using getPreviousPage('Business').

      action (SelectBusiness) {
    type (Search)
    collect {
    input (filterTerm) {
    type (BusinessFilterTerm)
    min (Optional) max (One)
    }

    //select from items in this list
    computed-input (business) {
    type (Business)
    min (Required) max (Many)
    compute {
    intent {
    goal: Business
    value-set: Business {$expr("getPreviousPage('Business')")}
    }
    }
    }
    }
    output (Business) {
    on-empty {
    flag-as-off-topic
    }
    }
    }

User and Device Information Functions

These functions return information about the user or their device.

  • getLocale(locale): Get locale-related information for a given locale identifier. Returns a node with the following properties:
    • country: ISO 3166-1 alpha-2 country code (for example: "US", "KR")
    • iso3Country: ISO 3166-1 alpha-3 country code (for example: "USA", "KOR")
    • language: ISO 639-1 language code (for example: "en", "ko")
    • iso3Language: ISO 639-2 language code (for example: "eng", "kor")
    • currencyCode: ISO 4217 currency code (for example: "USD", "KRW")
    • currencySymbol: currency symbol (for example: "$", "₩")
    • measurementSystem: one of USC, Imperial, or Metric

In this example, getLocale is used to determine the user's measurement system. The calculateDistance geospatial function is also used.

action (GetCurrentDistance) {
type (Fetch)

collect {
input (point) {
type (GeoPoint)
min (Required)
plan-behavior (Always)
}
}

output (Distance) {
evaluate {
if (getLocale($user.locale).measurementSystem == 'USC') {
Distance$expr(calculateDistance($user.currentLocation, point, 'Miles'))
} else {
Distance$expr(calculateDistance($user.currentLocation, point, 'Kilometers'))
}
}
}
}
  • permissionGranted(permission): Return true if the passed permission has been granted for the capsule. The available permission values are listed in the capsule.permissions reference key.
output (Something) {
evaluate {
if("#{permissionGranted('user-profile-access')}") {
...
} else {
...
}
}
}

For more information, see the Granting Capsule Permissions section of Preparing Your Capsule in the developer's guide.

Matching Functions

  • "test:values(node1, node2)": Return true if the data values of each node are equal, false if they are different values or different types.

  • "test:data(node1, node2)": Return true if the data values of each node are equal, ignoring their type.

  • min(node[, property]): Return the minimum value of the given node (using an optional property for comparison, if provided).

  • max(node[, property]): Return the maximum value of the given node (using an optional property for comparison, if provided).

    action (maxWithPropertyInAction) {
    type(Search)
    description (returns the employee with the max int)
    collect {
    input (structureOfNameIntBool) {
    type (StructureOfNameIntBool)
    min (Optional) max (Many)
    default-init{
    intent{
    goal: GetEmployees
    }
    }
    }
    }
    output (StructureOfNameIntBool){
    evaluate{
    $expr(max(structureOfNameIntBool, structureOfNameIntBool.name))
    }
    }
    }
  • compareAll(node1, node2): Compare two specified nodes, using default natural ordering. Returns a negative integer when node1 is less than node2, a positive integer when node1 is greater than node2, or zero if node1 and node2 are equal.

    For purposes of comparison, non-null is less than null and non-empty is less than empty. When comparing two nodes that have a different number of values, the one with fewer values is less than the one with more. Comparison of multi-value nodes of an equal number of values is made on a per-value basis. For example, with the following nodes:

      node1 = null/empty node
      node2 = ["yyy"]
      node3 = ["aaa", "bbb"]
      node4 = ["ccc", "ddd"]

    The following comparisons result:

      compareAll(node1, node1) == 0   // both are null/empty
      compareAll(node1, node2) == 1   // null/empty > non-null/non-empty
      compareAll(node2, node3) == -1  // node2 has fewer values
      compareAll(node4, node3) == 1   // same length, node4's values > node3's
      compareAll(node3, node3) == 0   // same length, equal values
    Note

    The compareAll() function replaces the now-deprecated older compare function, which cannot handle empty or multi-value nodes. You should update any existing capsule code that uses compare() to use compareAll().

  • compareSemVer(ver1, ver2) : Compares SemVer versions using the default natural ordering of version strings. The ver1 and ver parameters are different semantic version strings.

  • regexAllMatch(node, regex): Return true if (and only if) all values in the node match the specified regular expression, false if any values do not match.

  • regexAnyMatch(node, regex): Return true if any values in the node match the specified regular expression, false if no values match.

  • similarity(string1, string2): Return the 3-gram edit distance between two strings.

    This strategy uses similarity to help find localities the user is searching for:

      selection-strategy {
    id (locality-name-similarity)
    match {
    NamedPoint {
    from-output: ConstructNamedPointFromRegion {
    from-input: Locality (locality) {
    from-output: FindLocality {
    from-input: LocalityName (find)
    }
    }
    }
    }
    }
    named-advice ("rank") {
    advice ("${ similarity(locality.name, find) }")
    advise-against { lowerBoundClosed (0.0) upperBoundClosed (0.5) }
    advise-for { lowerBoundOpen (0.5) upperBoundClosed (1.0) }
    }
    }
  • editDistance(string1, string2): Return the Levenshtein distance between two strings.

Node Evaluation Functions

  • test:instanceOf(node, type): Return true if the value(s) of the specified node are instances of the specified type, false if they are not.

      if (test:instanceOf(region, 'viv.geo.LevelOneDivision')) {
    // region is an instance of viv.geo.LevelOneDivision
    }
  • test:assignableFrom(type1, type2): Return true if type1 is a valid type identifier and can be assigned from type2, false otherwise.

  • typedValue: Return an unattached node from a value.

      typedValue('contact.EmailType', ’home’)
  • size(node): Return the number of values contained in a node. A single-item node has a size of 1; a multi-item node, such as a result list, has a size of the list's length.

  • exists(node): Return true if the node has a value, false if it does not.

  • empty(node): Return true if the node has no value, false if it does. This function is the inverse of exists(): when one is true, the other is false.

  • isElement(node): Return true if the node is an element of an array. This occurs when the node is being accessed with an array-style iterator such as for-each and where-each, or when the user navigates from a summary layout to a detail layout. Note that isElement() always returns true if the node being tested is an array element, even if it is the only element in the array.

  • relaxed(node): Return true if one or more of the node's search constraints have been relaxed, false if they have not. See Relaxation for more about constraints and relaxation.

  • groupRelaxed(node): Return true if the node or any of its upstream input nodes have had any of their search constraints relaxed, false if none have been. See Relaxation for more about constraints and relaxation.

    Note

    The distinction between relaxed() and groupRelaxed() is that relaxed() only tests the specified node, while groupRelaxed() tests the upstream nodes that were part of the specified node's input.

  • plural(node): Returns the Unicode Common Locale Data Repository plural category of cardinal type for this node's value based on pluralization rules for the current locale. You can use this to choose whether and how to pluralize a concept name based on its bound value.

    For languages that have only two forms such as English, only two return values are possible, One or Other. Other languages, though, might return other values: the complete set of possible return values are Zero, One, Two, Few, Many, or Other. You must check the standard for your language to determine its possible values.

    In this example, "restaurant" or "restaurants" is chosen depending on the value of the Restaurant node, which will be One or Other. Note the return values are enums, not strings.

      dialog (Concept) {
    match: Restaurant (this)
    switch (plural(this)) {
    case (One) {
    template (restaurant)
    } default {
    template (restaurants)
    }
    }
    }
  • concept.plural(node) and concept.plural('string'): A form of plural() that overrides the pluralization logic built into Bixby's dialog system, explicitly forcing it to choose a specified pluralization, such as "restaurants" vs. "restaurant" as defined in the example above.

    You can pass either a string that corresponds to one of the enumerated values listed for plural(), or a value to use as the basis for pluralization.

      // the current locale's "Many concepts" pluralization, or the
    // closest analogue if "Many"'" does not apply to that locale
    #{value(concept.plural('Other'))}

    // the current locale pluralization for "2 concepts"
    #{value(concept.plural(2))}

    You can use this form to force one concept to take on the pluralization of another concept. For instance, a restaurant search capsule could have both a Restaurant model and a RestaurantStyle model with styles like pub, taqueria, cafe, and so on. To choose a pluralization for RestaurantStyle based on the value of Restaurant, you could write:

      Searching for #{concept(restaurantStyle.plural(plural(restaurant)))}...
  • state(node): Returns the evaluation state of this node, one of Pre (the node has not yet been evaluated), Mid (the node is currently being evaluated), or Post (the node has already been evaluated). This can be used to test or indicate Bixby's processing state. For instance, in an action that computes the distance between two points, state() could be used to customize dialog to indicate current progress:

      dialog (Action) {
    match: Distance (this) {
    from-output: GetDistance (action)
    }
    if (state(this) == 'Pre') {
    template ("compute the distance[ between #{value (action.point)} and #{value (action.relativePoint)}][ in #{value (action.unit.plural(2))}]")
    } else-if (state(this) == 'Mid') {
    template ("calculating the distance[ between #{value (action.point)} and #{value (action.relativePoint)}][ in #{value (action.unit.plural(2))}]")
    }
    }

Special EL Variables

There are several variables available in EL that can be used to access contextual information about the user and device.

  • $handsFree: set to true if the device operates in "hands-free" mode.

  • $sortOrder: set to one of ascending, descending, or unspecified.

  • $sortType: set to one of natural, compound, binding, explicit, or undefined.

  • $user: contains several properties with more information about the user:

    • $user.nickName: the user's nickname as a string, if set.
    • $user.currentLocation: the user's current location, as a GeoPoint.
    • $user.timeZoneId: the user's current timezone. (This may be null if the timezone is not set.)
    • $user.locale: contains the structure returned by getLocale() for the user's current locale (for example, $user.locale.country). See Locale Functions for the complete list.
    • $user.is24HourFormat: boolean indicating whether the device is set to display time in 24-hour format (true) or AM/PM format (false).
    • $user.networkCountry: the country of the current network, denoted in ISO 3166-1 alpha-2. Example: US or UK.
  • $can: contains several properties with more information about the environment, all based on the device identifier string:

    • $can.id: the identifier string for the device Bixby is running on. This is the same as the target string set in the simulator, and begins with a bixby prefix, followed by a device identifier such as mobile, followed by a locale. Example: bixby-mobile-en-US.
    • $can.device: the device identifier string without the locale. Example: bixby-mobile.
    • $can.locale: the current locale's identifier string with no device information. Example: en-US.
    • $can.language: just the two-character language identifier. Example: en.

These EL variables can be used, for example, as computed inputs to actions. Suppose you wanted to set a concept's value to the user's current location. You could set a myLocation concept to that value by creating an action file that takes this computed-input block:

computed-input (myLocation) {
min (Required) max(One)
type (geo.CurrentLocation)

compute {
if (exists($user.currentLocation)) {
intent {
goal: geo.CurrentLocation
value-set: geo.CurrentLocation { $expr($user.currentLocation) }
}
}
}
}