Use XPath to Concatenate Strings (Cleaner)

There have been times when you may need to take a list of strings and concatenate them together into a single string.

Think of a list of email address, concatenated by a semi-colon (;) like so:

As I’ve recently mentioned, loops can be slow … and the output messy, even with a simple “Append to string variable” action.

Just look at the output, double semi-colons (;;) where the blank values used to be … yucky!

clark.kent@dailypla.net;;lois.lane@dailypla.net;jimmy.olsen@dailypla.net;;perry.white@dailypla.net

Even the Data Operation action Join, while easier/faster that the loop, is still messy!

Same messy output as the loop, just faster.

I know what you’re thinking, it’s just a list of email addresses, double semicolons don’t really matter. True, but if you’re exporting the value to another system or doing some other processing on it, it may actually matter, especially if it’s some other value besides email addresses. Plus, nice-n-tidy clean strings are better to deal with. So …

XPath to the rescue!

Here’s how XPath can be used to make the output cleaner (and just as fast as the Join).

Using XPath’s translate(…) and normalize-space() functions

The key here is the combination of the translate(...) and normalize-space() XPath functions.

normalize-space() – grabs the text value from all nodes, reduces multiple spaces to a single space, then removes all beginning/trailing spaces from the final concatenated text value.

translate(...) – replaces the search character(s) with the specified character(s).

One thing to note here about the normalize-space() function. Since beginning/trailing spaces are removed, the text values are shoved together, like this:

clark.kent@dailypla.netlois.lane@dailypla.netjimmy.olsen@dailypla.netperry.white@dailypla.net

Shucks, now we don’t know where one value ends and the next begins.

To fix this, we simply inject in a trailing space after each value.

The XML will initially look like this

<r>
  <v>clark.kent@dailypla.net</v>
  <v>lois.lane@dailypla.net</v>
  <v>jimmy.olsen@dailypla.net</v>
  <v>perry.white@dailypla.net</v>
</r>

and after this expression to inject the trailing space

replace(string(xml(json(concat('{"r":{"v":', outputs('Strings'), '}}')))), '</v>', ' </v>')

the new XML will look like this (trailing spaces highlighted in blue)

<r>
  <v>clark.kent@dailypla.net </v>
  <v>lois.lane@dailypla.net </v>
  <v>jimmy.olsen@dailypla.net </v>
  <v>perry.white@dailypla.net </v>
</r>

Here is the complete XPath expression and the output:

xpath(xml(replace(string(xml(json(concat('{"r":{"v":', outputs('Strings'), '}}')))), '</v>', ' </v>')), 'translate(normalize-space(), " ", ";")')
Nice clean output of the XPath expression

Lastly, to show you how well the XPath vs Join vs loop performs:

Happy coding!


Follow My Blog

Get new content delivered directly to your inbox.

Avoid Loops with XPath

Iterating over an array of values to perform complex actions is easy with actions such as Apply to Each or Do Until within Power Automate.

But what if you simply want to concatenate some strings together? Or sum some numbers? Or do some other simplistic x, y, or z logic?

I already know about loops, take me to the goods! (tl;dr)

Here’s an example of a loop to sum an array of 15 numbers: [1,2,3,4,5,6,7,8,9,10,0.1,0.2,0.3,0.4,0.5]

4 seconds to sum 15 numbers??? Not good.

As you can see from the image above, even for relative simple operations, these looping actions can actually be quite expensive performance-wise for your flow. Four seconds or more to sum 15 numbers? Yikes. (I’ve even seen it take 6+ seconds, not good.)

Plus, loops can be hard to troubleshoot when things go wrong. How frustrating is it when you can’t expand the loop action or any of its children to see what the loop is currently doing while it’s running?

Shucks, we can’t expand child condition

That’s unfortunate, we can’t see what the loop is currently doing.

So, do I really need a loop?

Maybe, maybe not.

Going back to our inefficient number sum example above. Let’s think about how we can approach this a different way.

Here’s what we know:

  1. We have an array of values (numbers)
  2. We need a single output (sum)
  3. We don’t want to use a loop

Since we are dealing with an array (aka collection), the first thing to do is to see which actions and/or expressions we have that can target them.

Under the Data Operation category of the Power Automate actions list, there are some possible action candidates: Filter, Select, and Join which all deal with arrays. Although only one returns a single value, Join.

Join still is not what we need as it simply concatenates values together into a single string. It doesn’t allow us to do any math operations, like we need for summing numbers.

Next, let’s look at the expressions we have available in the dynamic content panel. Some possible candidates are: concat, union, join, add.

Ooh … add, but shucks it only takes 2 parameters, and array has 15 values. That won’t work.

Wait … here’s an interesting expression: xpath(...). What’s XPath?

XPath is an expression language that navigates and selects nodes in an XML document.

This sounds very similar to what a loop can do. Alright, we may be on to something here! Can XPath sum values? Why yes it can!

Expressions to the rescue!

Using XPath against our array requires us to convert it XML. This is easy with the xml(...) expression.

Unfortunately, arrays don’t directly convert to XML, so we need to massage it slightly.

XML expects a single element at the root of the document, and arrays are multiple elements. So we can simply wrap our array in a JSON object to accomplish this.

Assuming our array of numbers is in a compose action like this:

Our array of 15 numbers

Here’s the expression to wrap our number array:

json(concat('{"r":{"v":', outputs('Numbers'), '}}')))

Which produces this new object:

{
  "r": {
    "v": [1,2,3,4,5,6,7,8,9,10,0.1,0.2,0.3,0.4,0.5]
  }
}

With this new JSON object, “r” is now the root property, and it looks like this when we convert it to XML:

<r><v>1</v><v>2</v><v>3</v><v>4</v><v>5</v><v>6</v><v>7</v><v>8</v><v>9</v><v>10</v><v>0.1</v><v>0.2</v><v>0.3</v><v>0.4</v><v>0.5</v></r>

Now the XPath needed to sum the values looks like this:

sum(//v/text()[number(.) = number(.)])

Here’s what it is doing:

  1. sum(...) – this function adds element values together (our numbers),
  2. //v/text() – this selects all of the elements, the “v” nodes (our numbers), and returns their text value,
  3. number(.) = number(.) – this logic basically means “only when the value, converted to a number, equals a number” or something like that.

So, the complete XPath sum expression looks like this:

xpath(xml(json(concat('{"r":{"v":', outputs('Numbers'), '}}'))), 'sum(//v/text()[number(.) = number(.)])')

Here’s the output of the run of this flow action:

I feel the need, the need for speed!

Zero seconds! That’s better.

And if you really want to see the time differences, here are the metrics, using the start/end times of the actions:

Not too shabby XPath!

Tip to calculate how long an action took to execute (in seconds), use this expression:

div(sub(ticks(actions('XPath_Sum')?['endTime']),ticks(actions('XPath_Sum')?['startTime'])),float(10000000))

Takeaway

As you can see, using XPath is blazingly fast compared to the loop timing.

And as you increase the number of items, the loop tends to get slower, but XPath generally does not.

For example, summing a range of numbers 1 to 100: range(1, 100)

31 seconds to sum 1 to 100 … boo!
Still zero seconds!

Loops aren’t always necessary and can be a performance hog when used unnecessarily.

I encourage you, spend a little forethought on what your flow will do, and you can have it do mighty things, simpler and faster.

See my next post on how to generate cleaner concatenated strings using XPath.

Happy coding!


Follow My Blog

Get new content delivered directly to your inbox.