DEV Community

Cover image for Power Automate - Handling XML
david wyatt
david wyatt Subscriber

Posted on

Power Automate - Handling XML

Power Automate is built on JSON (JavaScript Object Notation), every input and output will be in a JSON format, and that's great because its the best 😎. Its a universal language we can use to talk to any other system though their API's, but its not the only language. In fact the original Dynamics (which Dataverse and the Power Platform is built on) wasn't JSON, but XML (its why you can still do XML quires on Dataverse tables). XML is still widely used, so this blog is all about how you can handle it in your flow.

  1. What is XML
  2. NameSpaces
  3. Xpath
  4. Updating XML

1. What is XML

what is xml

XML stands for 'Extensible Markup Language', and is a fully flexible markup language. Where HTML is also a markup language, it is not fully flexible as it has to follow the Hypertext agreed tags (you can think of the same way as a jacuzzi/hot tub, as HTML is a type of XML, but XML is not a type of HTML - wow random example there).

So XML allows you to use any tag (<tag></tag>) which is great for building custom data structures to send data between systems.

Pros/Cons

Why is XML still used, well it does have it's benefits, in particular:

Easy to read
XML is super easy to read, with the closing tag compared to a generic }, you can read nested XML.

It's everywhere
Although maybe 'dated' it isn't going away, there are API's that will not change, and there is the 800 pound elephant in the room, HTML. As I said HTML is XML, so if you want to handle HTML, you need to handle XML.

But there are also a couple of negatives to XML, especially compared to JSON, which is why Power Automate, and most new API's use it.

It's Verbose
Compared to JSON, there are more words/characters in the payload, which makes it slower to type and require more data:

<name>David Wyatt</name>
Enter fullscreen mode Exit fullscreen mode

json

"name":{David Wyatt}
Enter fullscreen mode Exit fullscreen mode

Open Structure
JSON does not allow duplicate keys in the same object, but XML does, which can cause problems when combining XML documents from different XML applications:

<developer>
   <name>David Wyatt</name>
   <tech>power platform</tech>
</developer>

<developer>
   <dev name>George Tzani</dev name>
   <role>Snr Dev</role>
   <location>UK</location>
</developer>
Enter fullscreen mode Exit fullscreen mode

To rectify above XML uses namespaces, which is great but again adds complexity.

2. Namespaces

Namespaces groups tags so that they can be identified/filtered. They are generally a url to documentation for the XML, just like a JSON will have a schema url.

You can see namespaces with the xmlns attributes in a tag, everything nested in the tag is in the namespace.

<?xml version="1.0" encoding="UTF-8"?>
<library
    xmlns="http://example.com/library">
    <book>
        <title>XML Basics</title>
        <author>John Doe</author>
    </book>
    <book>
        <title>Advanced XML</title>
        <author>Jane Smith</author>
    </book>
</library>
Enter fullscreen mode Exit fullscreen mode

You can have multiple namespaces inside the same tag, but then any tags linked to the namespace require a unique prefix linked to the namespace.

<?xml version="1.0" encoding="UTF-8"?>
<catalog
    xmlns:bk="http://example.com/books"
    xmlns:auth="http://example.com/authors">
    <bk:book>
        <bk:title>XML Basics</bk:title>
        <auth:author>John Doe</auth:author>
    </bk:book>
    <bk:book>
        <bk:title>Advanced XML</bk:title>
        <auth:author>Jane Smith</auth:author>
    </bk:book>
</catalog>
Enter fullscreen mode Exit fullscreen mode

3. Xpath

Xpath is the way to query/navigate the XML tree. If you think of each tag as a folder on your desktop, and you click through nested folders you see a path in the explorer window, xpath is this shortcut, but with some extra bells and whistles.

So if I wanted all the books in the below I would use /library/book

<?xml version="1.0" encoding="UTF-8"?>
<library>
    <book>
        <title>XML Basics</title>
        <author>John Doe</author>
    </book>
    <book>
        <title>Advanced XML</title>
        <author>Jane Smith</author>
    </book>
</library>
Enter fullscreen mode Exit fullscreen mode

That would return an array, so if I wanted the first I could use /library/book[0]. Also I can grab all books in any tag with a kind of wildcard of //book.

The structure of our xpath looks like this:

Expression Description
tag Selects all tags/nodes with the name "tagName"
/ Selects from the root tag
// Selects tags in the document from the current tag that match the selection no matter where they are
. Selects the current tag
.. Selects the parent of the current tag
@ Selects attributes

Xpath can also sum and other math expressions from arrays, its incredibly powerful and I could go on all day, but there are already great resources available like https://www.w3schools.com/xml/xpath_intro.asp.

There is one call out I want to make here, and that the above doesn't work the same with namespaces.

If the XML had prefixes then they can be added and it works, but if its a just a namespace then the xpath now looks something like:

/library/*[local-name()='book']
Enter fullscreen mode Exit fullscreen mode

If only one namespace in the entire XML, or if a specific namespace:

/library/*[local-name()='book' and namespace-uri()='http://example.com/library']
Enter fullscreen mode Exit fullscreen mode

Power Automate has an xpath function, but its not the only way to handle XML, in fact there are 3 ways.

4. Updating XML

  • String Manipulation
  • JSON Manipulation
  • XML Manipulation

In the below we are going to use this XML, and we want to remove the location tag (although these techniques will also work with updating/adding).

<?xml version="1.0" encoding="UTF-8"?>
<library
    xmlns="http://example.com/library">
    <location>UK</location>
    <book>
        <title>XML Basics</title>
        <author>John Doe</author>
    </book>
    <book>
        <title>Advanced XML</title>
        <author>Jane Smith</author>
    </book>
</library>
Enter fullscreen mode Exit fullscreen mode

String Manipulation

I will let you in on a secret, although the most 'bodgy' way to do it, this is kind of my favourite 😎

In this approach we are going to treat the XML as text and edit it. The best way to do that in Power Automate is the replace() function, but that doesn't allow regex's. So instead we are going to use the split() function approach. We are going to split the xml into 3, up to the location, the location, and after location, then put 1 and 3 back together.

string flow

The first expression splits the before and after location, creating our first array of 2 items.

split(triggerBody()['text'],'<location>')
Enter fullscreen mode Exit fullscreen mode

The second splits the second item into 2, and then concats the first item from the first array with the second item from the second array.

concat(
    outputs('Split_xml')[0]
,
    split(outputs('Split_xml')[1],'<location/>')[1]
)
Enter fullscreen mode Exit fullscreen mode

If we wanted to edit/add new tag, we would add our new tag between the above items.

JSON Manipulation

Power Automate uses JSON as its api language, its less verbose and the new standard. So in this approach we are going to convert to JSON, remove the tag, convert back to XML.

json flow

The first expression converts the XML to a JSON, but as the content is a string, not XML, we convert it to XML first.

json(xml(triggerBody()['text']))
Enter fullscreen mode Exit fullscreen mode

The second expression uses the removeProperty() function to remove the location tag. Though as its a nested tag (inside the library tag) we have to update the library property with the location property removed.

xml(setProperty(
    outputs('Convert_to_JSON')
,
    'library'
,
    removeProperty(
        outputs('Convert_to_JSON')?['library']
    ,
        'location'
    )
))
Enter fullscreen mode Exit fullscreen mode

Finally we convert it back to xml using the xml() function.

Here the edit/add tag is super easy, we just change the removeProperty() to setProperty() / addProperty().

XML Manipulation

This is the way we probably should do it, but for me its a little to clunky. We cant edit/add/remove properties like we can with a JSON, so we are going to have to do string manipulation too.

xml flow

First we are going to use the xpath() function to get our location tag, as with JSON conversion we also need to convert it to xml first. We use the below expression to get the tag:

xpath(
    xml(triggerBody()['text']))
,
    '//*[local-name()="location"]'
)[0]
Enter fullscreen mode Exit fullscreen mode

As it returns an array of all location tags we have to grab the first with the array position [0].

Call out, if you didn't select a specific item in the array, and went for the whole array, it would return a collection of base64 items (like files, showing $content-type and $content keys), so you would need to base64ToString() convert the $content.

One small problem, its also appends a attribute to the tag, xmlns="http://example.com/library", which normally wouldn't matter, but it will in this case (see later). Why does it do that, because of the namespace. As said before, if you didn't have the namespace then the below expression would work:

 xpath(xml(triggerBody()['text'])),'//location')
Enter fullscreen mode Exit fullscreen mode

If you wanted to target with a namespaces you would add it to the xpath, so ours would look like this:

/*[local-name()="location" and namespace-uri()="http://example.com/library"]/*[local-name()="location"]
Enter fullscreen mode Exit fullscreen mode

The only reason I didn't include the and namespace is that it isn't necessary, as there is only one namespace in the XML.

So why is this a problem, well when you use local-name() it automatically adds the namespace as a. attribute to our xpath() output (to help track it in the future). Again David why is this a problem, well because of the next step, we plan to use the xpath() output to do a find and replace (our way if creating a kind of regex), and that wont work because now it doesn't match the tag in the XML.

The namespace shouldn't be added without the namespace in the xpath(), but Microsoft decided to add it anyway. Finally getting to the point now, it means that we need to remove the namespace parameter from our xpath() output, and you guessed it, the best way is to convert it to a string and replace it with ''.

replace(
    string(
        xpath(
            xml(triggerBody()['text'])
        ,
            '//*[local-name()="location"]'
        )[0]
    )
,
    ' xmlns="http://example.com/library"'
,
    ''
)

Enter fullscreen mode Exit fullscreen mode

Callout, see that leading whitespace of xmkns, that's there on purpose, do not remove it

Now we have out tag that is an exact match, our second expression uses a replace() expression to replace it with ''.

replace(
    triggerBody()['text'])
,
    outputs('Get_Protection')
,
    ''
)
Enter fullscreen mode Exit fullscreen mode

If you want to add a tag you could concat it to the xpath() output, and if you want to edit it you could just edit/rebuild the xpath() output.


All 3 ways to handle XML work, but in most cases I would say JSON convert is the best, with XML for any particularly awkward schemas, but I still love a good string split() 😎.

The flow can be found in a solution named XML here.

Top comments (1)

Collapse
 
balagmadhu profile image
Bala Madhusoodhanan

The complexity of mapping would be when you have NESTED data mapped in XML . Had to leverage C# custom code to unpack the information .