...
- First, recognize that the entire ruleset can be seen as a big if-statement looping over the event stream. Seeing event processing as a looping process helps design efficient, effective rulesets.
- Also, JPexs provide a way of processing arrays in large data sets that amounts to a kind of implicit looping in many cases.
- Many of the array and map operators like map() and filter() loop over data.
- In addition to these implicit forms of looping, KRL functions support recursion and thus can be used to iterate over arguments.
But with all that, we sometimes need explicit loops. KRL loops are a little different than what your previous programming experience might lead you to expect. The KRL foreach
statement can only appear just below the select
statement like so:
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
select when pageview url "/archives/" foreach [1, 2, 3] setting (x) |
...
The value following the keyword foreach
can be any KRL expression that yields an array or map. If the variable f
were bound to a JSON data structure, you could use a pick and JPex like so:
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
select when pageview url "/archives/" foreach f.pick("$..store") setting (x) |
To use the foreach
statement with a map, you provide two variables in the setting
clause that will be bound the name and value of that entry in the map:
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
select when pageview url "/archives/" foreach {"a" : 1, "b" : 2, "c" : 3} setting (n,v) |
...
You can have more than one loop in a rule by simply nesting one foreach
inside another:
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
select when pageview url "/archives/" foreach [1, 2, 3] setting (x) foreach ["a", "b", "c"] setting (y) |
...
Comparison of FLWOR and KRL
FLWOR | KRL |
---|---|
Foreach | Foreach |
Let | Prelude |
Where | Rule condition |
Order | Array filters and operations |
Result | Action |
The entire rule body—everything after the select
—is executed once for every loop. If the rule condition is true, an action is produced, so a rule with a foreach
over a three-element array would produce three actions if the condition were true each time. (Note: KRL optimizes rule preludes by automatically moving expressions that don’t depend on the variable being set in the foreach statement outside the loop during execution so that only those things that really need to be executed multiple times are.)
...
While this kind of data would generally be generated, let’s just declare it, in a variable named items, in the global:
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
global { items = [{page: "baconsalt.com" content: "Hello World. Go Bacon." header: "Bacon Salt Test" }, {page: "craigburton.com" content: "Hello World. Burtonian methods." header: "Craig Burton Test" }, {page: "kynetx.com" content: "Hello World. The World According to Kynetx" header: "Kynetx Test" }] } |
...
Your first attempt, using foreach
might look something like this:
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
rule using_foreach { select when pageview url ".*" foreach items setting (d) pre { h = d.pick("$.header") + " using foreach"; c = d.pick("$.content"); domain = page:url("domain"); } if(domain eq d.pick("$.page")) then notify(h,c); } |
...
The first is to use the full power of array filters to cut the array down to just those members meeting the desired criterion:
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
rule using_foreach_with_filter { select when pageview url ".*" foreach items.filter( function(x) {page:url("domain") eq x.pick("$.page")}) setting (d) pre { h = d.pick("$.header") + " using foreach"; c = d.pick("$.content"); domain = page:url("domain"); } if(domain eq d.pick("$.page")) then notify(h,c); } |
...
In the following rule, we use the implicit looping and filtering capabilities of a JPex to find just the item we want from the data structure and then pick the pieces we need out of that one item.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
rule without_foreach { select when pageview url ".*" pre { dom = page:url("domain"); item = site_data.pick( "$..items[?(@.page eq '"+dom+"')]"); content = item.pick("$..content"); header = item.pick("$..header") + " without foreach"; } if(dom eq item.pick("$..page")) then notify(header,content); } |
...
Suppose we wanted to make changes to a page based on data we retrieved from an online datasource. Assume the datasource returns data like the following and we bind it to a variable named replacements
:
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
{"desc": "Data set to test foreach", "replacements": [ {"selector":"#categories", "text":"This was the cloud tag" }, {"selector":"#friends", "text":"This was a list of friends" }, {"selector":".action-stream", "text":"This is where the action stream was" } ] } |
In this dataset, we have an array of replacements, each of which contains a jQuery selector pattern for elements on a Web page and the text we’re going to use with the action. The following ruleset uses the items in this data structure to prepend the text in each item above to the element on the page that matches the associated selector pattern:
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
rule prepend { select when pageview url "windley.com" foreach replacements.pick("$.replacements") setting (r) pre { sel = r.pick("$.selector"); new_text = r.pick("$.text"); } prepend(sel,new_text); } |
...
Using data often requires loops. We'v seen that there are multiple ways to loop in KRL: a ruleset is a loop, each rule can loop explicitly using foreach, and implicit looping is accomplished using operators like pick()
, map()
, and filter()
.
...