XML

You can automatically include dynamically-generated content into your campaign by specifying a URL to a remote XML file. The contents of this file will automatically be made available to your email template where you can access it using XPath expressions.

When you provide the remote XML URL:

  • Ensure the URL is a valid HTTP or HTTPS URL.

    Tip: it is possible to use 3rd party software to merge multiple feeds together.
  • Make sure your file is smaller than 10 MB. Anything larger than this will cause issues within the platform.
  • Make sure your XML file doesn’t specify a namespace with an xmlns declaration. This isn’t supported by our system for security reasons.

Tip: as long as it is valid XML, the remote XML file may be RSS.
Note: we may cache the XML content for up to 10 minutes to ensure good performance and prevent unnecessary requests. However if you set a caching HTTP header (eg cache-control or Expires) that’s longer than 10 minutes we will honour it.

Processing XML

These are the methods that can be called on xml nodes and their function.

Method Function
findnodes Returns a node (or node list)
findvalue

Returns a string value

Tip: If findvalue represents several tags, then the output will be text formatted with space. It is therefore advisable to only use findvalue for single tags.

find Does the 'right thing' and returns either text or a node
getAttribute Gets the attribute string

To process the XML you need an Xpath expression. The three main methods are expanded upon here with examples.

findnodes

Accepts and XPath expression and returns a node (or node list).

Example, to return all book nodes:

Copy
[* books = remote.xml.findnodes('//book') *]

Returns catalog:

Copy
[* content = remote.xml.findnodes('/catalog') *]

findvalue

Accepts and XPath expression and returns a string value. Best used for single tags.

Example, to return a string value:

Copy
[* book = remote.xml.findvalue("//book[@id='bk002']") *]
[* title = book.findvalue('title') *]

Returns:

Copy
[* remote.xml.findvalue('/book') | html *]

Note: The findvalue code is case sensitive, in the above example it would be looking for 'title' within the feed, if your feed uses 'Title' then this won't work. You must use the exact name within the findvalue code.

find

Does the "right thing" and returns either text or a node.

Example, to return all book nodes:

Copy
[* remote.xml.find('//book') *]

Example XML

Copy
<?xml version="1.0"?>
<catalog>
  <book id="bk001">
    <author>Tolstoy, Leo</author>
    <title>War and Peace</title>
    <genre>Classics</genre>
    <price>11.99</price>
  </book>
  <book id="bk002">
    <author>Murakami, Haruki</author>
    <title>Norwegian Woods</title>
    <genre>Bildungsroman</genre>
    <price>8.99</price>
  </book>
  <book id="bk003">
    <author>Nesbo, Jo</author>
    <title>The Bat</title>
    <genre>Crime</genre>
    <price>6.99</price>
  </book>
</catalog>

Iterating

You can use a FOREACH loop to act upon each item within your data, as well as use NEXT and LAST as flow control for the loop.

FOREACH

Example of looping over nodes, to return chosen tags:

Copy
[* books = remote.xml.findnodes('//book') *]
[* FOREACH book IN books *]
  [* title = book.findvalue('title') *]
  [* price = book.findvalue('price') *]
  [* id = book.getAttribute('id') *]

    <h1>[* title *]</h1>
    <p> [* price *], [* id *]</p>

[* END *]

NEXT

Example of using NEXT within a FOREACH loop, to skip a record if a condition is met:

Copy
[* books = remote.xml.findnodes('//book') *]
[* FOREACH book IN books *]

  [* title = book.findvalue('title') *]
  [* price = book.findvalue('price') *]
  [* genre = book.findvalue('genre') *]
  [* id = book.getAttribute('id') *]
    [* NEXT IF genre == 'Crime' *]

    <h1>[* title *]</h1>
    <p> [* price *], [* id *]</p>

[* END *]

LAST

Example of using LAST within a FOREACH loop, to escape FOREACH loop when condition is met:

Copy
[* books = remote.xml.findnodes('//book') *]
[* FOREACH book IN books *]

  [* title = book.findvalue('title') *]
  [* price = book.findvalue('price') *]
  [* genre = book.findvalue('genre') *]
  [* id = book.getAttribute('id') *]
    [* LAST IF genre == 'Bildungsroman' *]

    <h1>[* title *]</h1>
    <p> [* price *], [* id *]</p>

[* END *]

Using persist to speed up complex templates

The persist mechanism can be used to store the result of complex processing between iterations, which reduces repeated work and speeds up launches. This can significantly improve performance for some campaign types, most notably those that refer to external XML files, as it avoids the need to run potentially slow XPATH queries to find the content from the XML feed for every contact on the list.

Typically, the processing work is placed at the top of the template, so it will run if there’s no persisted data. Here’s an example that extracts articles from an RSS feed:

Copy
[*
    # Create articles array if we don't already have it
    UNLESS persist.news_articles;
    
        # Declare an empty array to store articles
        news_articles = [];

        # Iterate throught items in the RSS feed
        FOREACH item IN remote.xml.findnodes('//rss/channel/item');

            # Create a hash for the values required for each article
            article = {
                title       = item.findnodes('title').textContent,
                description = item.findnodes('description').textContent,
                url         = item.findnodes('link').textContent,
                pubdate     = item.findnodes('pubDate').textContent,
            };

            # Add article to array
            news_articles.push(article);
        END;

        # Store articles array for future iterations
        persist.news_articles = news_articles;
    END;
*]

Then later in the template, to output the articles, you can iterate the array created earlier:

Copy
[*FOREACH article IN persist.news_articles*]
    <h1><a href="[*article.url | html*]">[*article.title | html*]</a></h1>
    <p>[*article.description | html*]</p>
    <p><a href="[*article.url | html*]">Link</a></p>
    <p>[*article.pubdate | html*]</p><br><br><br>
[*END*]

Caution: Be careful to not include any contact-specific or PII (personally identifiable information) data in any of the persisted data structures, as this may lead to data being sent to the wrong contact.