Basix XML Expression Evaluation
Some XML Elements like Set, Switch, If, Cond make use of attribute 'expr' to permit to make decisions based on expression evaluation.
For example:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<IVR> <GetDigits numDigits="1" validDigits="0123456789"> <Speak voice="en-US-Standard-C">Please press one key</Speak> </GetDigits> <If expr="Digits == '7'"> <Then> <Speak voice="en-US-Standard-C">You pressed the lucky number 7</Speak> </Then> <Else> <Speak voice="en-US-Standard-C">You pressed {{Digits}} which multiplied by 3 gives {{int(Digits) * 3}}</Speak> </Else> </If> </IVR> |
Also, as shown above the result of expression evaluation can be interpolated into strings using handlebars.
There are several operators and functions that can be used in expression evaluations. They are listed here.
Operators
+ (adds two values):
"1 + 1" -> 2
- (subtract two values):
"100 - 1" -> 99
/ (divide one value by another):
"100/10" -> 10
* (multiple one value by another):
"10 * 10" -> 100
** (power):
"2 ** 10" -> 1024
% (modulus):
"15 % 4" -> 3
== (equals):
"15 == 4" -> False
in (is something contained within something else?):
"'one' in ['two', 'three']" -> false
GT, GE, LT, LE (DEPRECATED)
Since XML syntax uses '<' and '>' to indicate tags, we cannot freely use expressions like this:
"myvar < 10"
and we would need to escape them like this:
"myvar < 10"
which is confusing.
So we provide the following operators:
GT : > (greater than)
GE : >= (greater than or equal to)
LT : < (less than)
LE : <= (less than or equal to)
and so they can be used like this:
"myvar < 10"
DEPRECATED: the support for GT/GE/LT/LE will be eventually removed.
Instead use the reverse comparison like this:
"10 > myvar"
The above will work because only '<' is not freely allowed in XML.
or use a negated expression:
"not (myvar > 10)"
Functions
str(x)
converts some value to a string:
"str(123)" => '123'
int(s)
converts a numeric string into an integer:
"int('123')" => 123
float(s)
converts a numeric string into a float:
"float('123.4')" => 123.4
len(x)
returns the length of a string or list
"len('abc')" => 3
now()
gets current date/time as a string:
"dt = now()" => '2021-03-29 10:32:45'
year(dt)
extracts the year from a date/time string:
"year('2021-03-29 10:32:45')" => 2021
month(dt)
extracts the month from a date/time string:
"month('2021-03-29 10:32:45')" => 3
day(dt)
extracts the day of the month from a date/time string:
"day('2021-03-29 10:32:45')" => 29
hour(dt)
extracts the hour from a date/time string:
"hour('2021-03-29 10:32:45')" => 10
minute(dt)
extracts the minute from a date/time string:
"minute('2021-03-29 10:32:45')" => 32
second(dt)
extracts the second from a date/time string:
"second('2021-03-29 10:32:45')" => 45
month_day(dt)
generates a list [month, day] from a date/time string:
`"month_day('2021-03-29 10:32:45')" => [3,29]
month_name(dt)
returns the name of the month from a date/time string:
"month_name('2021-03-29 10:32:45')" => 'March' ('January' | 'February' | ... | 'December')
day_of_week(dt)
returns the day of the week from a date/time string:
"day_of_week('2021-03-29 10:32:45')" => 'MON' ('MON' | 'TUE' | 'WED' | 'THU' | 'FRI' | 'SAT' | 'SUN')
day_of_year(dt)
returns the day of the year from a date/time string:
"day_of_year('2021-02-01 10:32:45')" => 32
date_time_diff(dt1, dt2)
returns the difference in seconds between two date/time strings:
"date_time_diff('2021-02-01 10:32:55', '2021-02-01 10:32:45)" => 10
date_time_format(dt, format)
returns a string with a date/time formated as specified:
"date_time_format('2021-02-01 10:32:55', '%m/%D/%Y')" => '02/01/2021'
midnight_offset(offset, dt)
Returns 00:00:00 of the next day (relative to dt or now()), then adds/subtracts offset in seconds:
"midnight_offset(60)" => 'YYYY-MM-DD 00:01:00' of tomorrow (relative to now)
"midnight_offset(-10, '2025-09-18 21:37:49')" => '2025-09-18 23:59:50'
"midnight_offset(-30, '2025-09-20 12:34:58')" => '2025-09-20 23:59:30'
Obs: this function is currently not available. It will be usable from the next release.
time_in_range(dt, start, end)
returns true if the provided date/time string is within the specified range:
"time_in_range('2021-02-01 10:32:55', '10:00:00', '12:00:00')" => true
Obs: the start is inclusive and the end is exclusive so:
"time_in_range('2021-02-01 12:59:59', '10:00:00', '13:00:00')" => true
but:
"time_in_range('2021-02-01 13:00:00', '10:00:00', '13:00:00')" => false
match_time_in_range(dt, start, end)
returns data associated with a time range if the provided date/time string is within its specified range:
"match_time_in_range('2021-02-01 22:32:55', [['09:00:00','22:00:00','data1'], ['22:00:00','17:00:00','data2'], ['17:00:00', '09:00:00','data3']])" => 'data2'
get_entry(d, key, default=null)
get entry from a JSON object:
"get_entry({'a': 1, 'b': 2}, 'b')" => 2
"get_entry({'a': 1, 'b': 2}, 'z', 'NOT_FOUND')" => 'NOT_FOUND'
contains(s, subs)
checks if a string contains a substring:
"contains('aabbcc', 'bb')" => true
contains_any_of(s, substring_list)
checks if a string contains any substring within a list:
"contains_any_of('aabbcc', ['bb', 'cc'])" => true
iif(expr, true_part, false_part)
returns one of two parts, depending on the evaluation of an expression:
"iif(5 > 7, 'yes', 'no')" => 'no'
shuffle(l)
shuffles the elements in a list:
"shuffle([1,2,3])" => [3,1,2] (but it would be random)
sort(l)
sort the elements in a list:
"sort([3,1,2])" => [1,2,3]
split(s, separator)
splits a string on a separator:
"split('a,b,c', ',')" => ['a', 'b', 'c']
join(l, separator)
joins a list into a string:
"join(['a','b','c'], ',')" => 'a,b,c'
jq_first(o, query)
gets the first element of an object that matches the query:
"jq_first({'a': {'b': {'c': 10}}, '.a.b.c')" => 10
"jq_first({'a': {'b': {'c': 10}}, '.a.b.ccc')" => null
(see jq-tutorial)
jq_all(o, query)
gets all element of an object that matches the query:
"jq_all({'a': {'b': {'c': 10}}, '.a.b.c')" => [10]
"jq_all({'a': {'b': {'c': 10}}, '.a.b.ccc')" => []
(see jq-tutorial)
shift(l)
removes the first element of a list (it changes the input parameter) and returns it:
"shift([1,2,3])" => 1
unshift(l, element)
add an element to the beginning of a list (it changes the input parameter):
"unshift([1,2,3], 0)" => [0,1,2,3]
pop(l)
removes the last element of a list (it changes the input parameter) and returns it:
"pop([1,2,3])" => 3
push(l, element)
add an element to the end of a list (it changes the input parameter):
"push([1,2,3], 4)" => [1,2,3,4]
lowercase(s)
converts a string to lowercase:
"lowercase('ABC')" => 'abc'
uppercase(s)
converts a string to uppercase:
"uppercase('abc')" => 'ABC'
hiraganize(s)
converts a japanese string to hiragana:
"hiraganize('横浜')" => 'よこはま'
katakanize(s)
converts a japanese string to katakana:
"katakanize('横浜')" => 'ヨコハマ'
starts_with(s, head)
checks if a string starts with a substring (the 'head')
"starts_with('09011112222', '090')" => true
ends_with(s, tail)
checks if a string ends with a substring (the 'tail')
"ends_with('09011112222', '2222')" => true
substr(s, start, length)
returns a substring of the given string 's' starting from index 'start' with length 'length'`
"substr('09012345678', 3, 4)" => '1234'
substring(s, start, end)
returns a substring of the given string 's' starting from index 'start' and ending at index 'end'
"substring('09012345678', 3, 7)" => '1234'
match_string(input_string, regex_string)
checks if the provided regular expression matches the input string
"match_string('09012345678', '^(090|080|070)')" => true
url_encode(text)
url encodes text
"url_encode('日本')" => '%E6%97%A5%E6%9C%AC'
url_decode(text)
url decodes text
"url_decode('%E6%97%A5%E6%9C%AC')" = '日本'
Counter Functions
Counter functions are used to control features by incrementing, decrementing, setting/unsetting counters/flags stored in our system.
set_counter('NAME', VALUE)
sets a counter to a value
"set_counter('some_counter', 10)" => true
get_counter('NAME')
gets the value of a counter
"get_counter('some_counter')" => 10
inc_counter('NAME')
increases a counter
"inc_counter('some_counter')" => 11
inc_counter_max('NAME', MAX)
tries to increase the counter. Successful only if MAX was not reached yet.
"inc_counter_max('some_counter', 100)" => 12
dec_counter('NAME')
decreases a counter
"dec_counter('some_counter')" => 11
dec_counter_min('NAME', MIN)
tries to decrease the counter. Successful only if MIN was not reached yet.
"dec_counter_min('some_counter', 0)" => 10
The inc_counter, dec_counter, inc_counter_max and dec_counter_min functions are atomic meaning that even if we have multiple calls arriving at the same time, they are ensured to get a different result (unless the max/min limits are reached)
Examples
Example 1: limit a feature by a counter (maximum of 80)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<IVR> <If expr="inc_counter_max('promotion_x', 80)"> <Then> <Speak/> <AddJob>{ "type": "msg_call", "data": { "calling_number": "{{CalledNumber}}", "called_number": "{{CallingNumber}}", "audio_file": "some_message.wav" } }</AddJob> <If expr="JobAdded"> <Then> <Speak>Thank you for your call. We'll call you back in a few seconds.</Speak> </Then> <Else> <Speak>Sorry, something went wrong. Bye</Speak> </Else> </If> </Then> <Else> <Speak>Sorry our limit for today has been reached. Try again tomorrow.</Speak> </Else> </If> </IVR> |
If this is a daily feature, the counter should be reset daily (eventually we will provide an automation feature for this: http://redmine.brastel.com/redmine/issues/249772)
Meanwhile the counter can be reset using an admin IVR calling function set_counter or via Basix PBX API (TBD: http://redmine.brastel.com/redmine/issues/249771) or done ad hoc using cron.
Example 2: controlling a feature when a counter reaches a certain value
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<IVR> <If expr="match_string(CallingNumber, '^(090|080|070)')"> <Then> <Speak>Sorry, we only accept calls from mobile numbers.</Speak> <Hangup/> </Then> </If> <If expr="inc_counter('promotion_X') == 10000"> <Then> <Speak>You are our caller number 10000.</Speak> <AddCdrAction action_name="some_cdr_action">DETAILS</AddCdrAction> <SendSMS mobile_number="{{CallingNumber}}">Here are the instructions to redeem your gift: ...</SendSMS> </Then> <Then> <Speak>Good luck next time.</Speak> </Then> </If> </IVR> |
Example 3: Special processing on every 1000 calls
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<IVR> <Set var="c" expr="inc_counter('promotion_X')"/> <If expr="c != 0 and c % 1000 == 0"> <Then> <Speak>You are our caller number {{c}}.</Speak> <AddCdrAction action_name="some_cdr_action">DETAILS</AddCdrAction> <SendSMS mobile_number="{{CallingNumber}}">Here are the instructions to redeem your gift: ...</SendSMS> </Then> <Else> <Speak>Good luck next time.</Speak> </Else> </If> </IVR> |
Example 4: simple service flag
1 2 3 4 5 6 7 8 9 10 11 |
<IVR> <If expr="get_counter('service_x_allowed') == 1"> <Then> <Speak>Welcome.</Speak> <Transfer>SOME_DESTINATION</Transfer> </Then> <Else> <Speak>Sorry, this service is not available at the moment.</Speak> </Else> </If> </IVR> |
The counter can be set using an admin IVR calling function set_counter or via Basix PBX API (TBD: http://redmine.brastel.com/redmine/issues/249771)
