BuildRowSetFromXML
Parses an XML string using an XPath expression and returns a rowset. Runtime-proven on a live Marketing Cloud Engagement CloudPage — including the third argument, which works the opposite way round from the documented syntax.
Syntax
BuildRowSetFromXML(xmlData, xpathExpression, returnEmptyOnError) → rowset
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
xmlData |
string | Yes | The XML payload to parse |
xpathExpression |
string | Yes | XPath expression selecting the nodes to turn into rows |
returnEmptyOnError |
boolean | number | Yes | Pass 1 or true for an empty rowset when the payload or path cannot be parsed |
Example
%%[
VAR @xml, @rows
SET @xml = '<root><Flight origin="IND">100.00</Flight></root>'
SET @rows = BuildRowSetFromXML(@xml, "//Flight", 1)
]%%
Rows: %%=v(RowCount(@rows))=%% origin: %%=v(Field(Row(@rows, 1), "origin_att", 0))=%%
Renders Rows: 1 origin: IND.
Because untrusted input yields an empty rowset rather than a value you can test, guard the read with RowCount, and use the three-argument Field form for attribute columns that some nodes may not carry:
%%[
VAR @rows, @row
SET @rows = BuildRowSetFromXML(@payload, "//Flight", 1)
IF RowCount(@rows) > 0 THEN
SET @row = Row(@rows, 1)
]%%
Price: %%=v(Field(@row, "Value", 0))=%% carrier: %%=v(Field(@row, "carrier_att", 0))=%%
%%[ ENDIF ]%%
Return value
rowset — one row per node the XPath expression matched.
There is no closed set of sentinel values: an unparsable payload, an empty string, an unset variable, an empty root element and an XPath that matches nothing all produce a rowset of zero rows when the third argument is 1, so RowCount is the only value worth branching on.
Behaviour
An XPath selecting sibling elements gives one row per matched element. //Flight over three Flight elements produced three rows.
Each row carries a Value column and an Xml column. Value holds the element’s own text and Xml holds its inner markup — for a plain element both read the same (200.00), but where the element has a child node Value read 500 while Xml also carried the child markup.
Every attribute seen on any matched node becomes a <name>_att column. The second flight read IND from origin_att and UAL from carrier_att. Lookup is case-insensitive: Origin_att returned the same value as origin_att. A node missing an attribute its siblings carry reads empty in that column rather than aborting — use the three-argument Field form for it.
Ordinals run Value, then Xml, then the attribute columns. On the second flight, ordinals 1–5 gave 200.00, 200.00, IND, LAX and UAL.
An XPath selecting an attribute node gives one row. /root/Flight[1]/@origin produced a single row whose Value and ordinal 1 both read IND.
Rowsets are read 1-based. Row(rowset, 0) and Field(row, 0) both abort the page, and the two-argument Field form on a missing column aborts too — prefer the three-argument form with 0 for anything that may be absent. RowCount never aborts and returns 0 for an empty rowset.
Either capitalisation resolves to the same function. BuildRowsetFromXML with a lowercase s produced identical results.
The third argument is inverted
The Syntax section presents a false third argument as the one that returns an empty rowset and a true one as the one that raises. At runtime it is the other way round.
| Call | Result |
|---|---|
BuildRowSetFromXML("<root><a>1</a>", "//a", 1) |
rowset with zero rows |
BuildRowSetFromXML("<root><a>1</a>", "//a", 0) |
HTTP 422, page aborted |
The boolean literal true behaves exactly like the number 1. Read the argument as return empty on error: pass 1 (or true) for any payload you do not control, then branch on RowCount. The full finding is on Differs from official docs, and the JSON sibling behaves identically.
Show test script
%%[
VAR @b, @xml, @unset, @rows, @r2, @r3
SET @b = RequestParameter("b")
SET @xml = '<root><Flight origin="IND" dest="NYC" carrier="UAL">100.00</Flight><Flight origin="IND" dest="LAX" carrier="UAL">200.00</Flight><Flight origin="IND" dest="SEA">500<PerBagSurcharge>25</PerBagSurcharge></Flight></root>'
/* control branch: proves the page itself renders */
IF @b == "zzz" THEN
OutputLine(Concat("CTRL=[alive]"))
ENDIF
/* one row per matched element, Value and Xml columns */
IF @b == "safe" THEN
SET @rows = BuildRowSetFromXML(@xml, "//Flight", 1)
SET @r2 = Row(@rows, 2)
OutputLine(Concat("x1 rc=[", RowCount(@rows), "]"))
OutputLine(Concat("x1 r2 Value=[", Field(@r2, "Value", 0), "] Xml=[", Field(@r2, "Xml", 0), "]"))
ENDIF
/* attribute columns, case-insensitive lookup, ordinal order */
IF @b == "attributes" THEN
SET @rows = BuildRowSetFromXML(@xml, "//Flight", 1)
SET @r2 = Row(@rows, 2)
OutputLine(Concat("x2 origin_att=[", Field(@r2, "origin_att", 0), "] Origin_att=[", Field(@r2, "Origin_att", 0), "] carrier_att=[", Field(@r2, "carrier_att", 0), "]"))
OutputLine(Concat("x2 ord1=[", Field(@r2, 1), "] ord2=[", Field(@r2, 2), "] ord3=[", Field(@r2, 3), "] ord4=[", Field(@r2, 4), "] ord5=[", Field(@r2, 5), "]"))
ENDIF
/* a node lacking an attribute the siblings carry, plus a child element */
IF @b == "missingattr" THEN
SET @rows = BuildRowSetFromXML(@xml, "//Flight", 1)
SET @r3 = Row(@rows, 3)
OutputLine(Concat("x3 r3 Value=[", Field(@r3, "Value", 0), "] Xml=[", Field(@r3, "Xml", 0), "]"))
OutputLine(Concat("x3 r3 carrier_att=[", Field(@r3, "carrier_att", 0), "]"))
ENDIF
/* an XPath selecting an attribute node */
IF @b == "attrpath" THEN
SET @rows = BuildRowSetFromXML(@xml, "/root/Flight[1]/@origin", 1)
OutputLine(Concat("x4 attr-xpath rc=[", RowCount(@rows), "] v=[", Field(Row(@rows, 1), "Value", 0), "] ord1=[", Field(Row(@rows, 1), 1), "]"))
ENDIF
/* everything that yields zero rows while the third argument is 1 */
IF @b == "empties" THEN
OutputLine(Concat("x6 malformed-flag1 rc=[", RowCount(BuildRowSetFromXML("<root><a>1</a>", "//a", 1)), "]"))
OutputLine(Concat("x8 emptystring-flag1 rc=[", RowCount(BuildRowSetFromXML("", "//Flight", 1)), "]"))
OutputLine(Concat("x5 emptyroot rc=[", RowCount(BuildRowSetFromXML("<root></root>", "//Flight", 1)), "]"))
OutputLine(Concat("x13 unsetvar rc=[", RowCount(BuildRowSetFromXML(@unset, "//Flight", 1)), "]"))
OutputLine(Concat("x9 nomatch rc=[", RowCount(BuildRowSetFromXML(@xml, "//NoSuchNode", 1)), "]"))
ENDIF
/* the boolean literal true behaves like the number 1 */
IF @b == "boolflag" THEN
OutputLine(Concat("x12 boolliteral rc=[", RowCount(BuildRowSetFromXML(@xml, "//Flight", true)), "]"))
ENDIF
/* the alternate capitalisation resolves to the same function */
IF @b == "casing" THEN
OutputLine(Concat("x11 lowercase-s-casing rc=[", RowCount(BuildRowsetFromXML(@xml, "//Flight", 1)), "]"))
ENDIF
/* fetch ?b=abortflag0 - HTTP 422, nothing renders */
IF @b == "abortflag0" THEN
OutputLine(Concat("--- x7 start ---"))
SET @rows = BuildRowSetFromXML("<root><a>1</a>", "//a", 0)
OutputLine(Concat("x7 rc=[", RowCount(@rows), "]"))
ENDIF
]%%
OutputLine given a bare string literal renders an empty line. Wrap the argument in Concat() or your start and done markers vanish silently — which looks exactly like the function failing.
Availability
| Platform | Available |
|---|---|
| Marketing Cloud Engagement | Yes |
| Marketing Cloud Next | No |
See also
- Differs from official docs — the inverted third argument in full
BuildRowsetFromJSON— the same shape for JSON payloads, with the same inverted flagBuildRowSetFromString— splits a delimited string into a rowset- Official reference · ampscript.guide