XML Wrapper
XmlWrapper¶
New Feature
Since 6.10.15
Provides utility methods for manipulating xml. On the following methods, wrap is a XmlWrapper.
Tip
- to convert the Xml to a string, use the
toStringmethod or define the variable as a String
Reading¶
get¶
Retrieves the wrapped org.w3c.dom.Element
Syntax
org.w3c.dom.Element wrap.get()
Example
final value = p6.wrap.of '<root><fee>Fee</fee><foo>Foo</foo></root>' asXml() get()
toString¶
Retrieves the String representation of the wrapped value.
Syntax
String wrap.toString()
Example
final s = p6.wrap.of '<root><fee>Fee</fee><foo>Foo</foo></root>' asXml() toString()
final String s = p6.wrap.of '<root><fee>Fee</fee><foo>Foo</foo></root>' asXml()
name¶
New Feature
Since 6.10.24
Returns the local (tag) name of the wrapped element.
Syntax
StringWrapper wrap.name()
Example
final String value = p6.wrap.of '<order id="42"/>' asXml() name()
assert value == 'order'
text¶
New Feature
Since 6.10.24
Returns the text content of the wrapped element as a StringWrapper (chainable, unlike toString()).
Syntax
StringWrapper wrap.text()
Example
final String value = p6.wrap.of '<root><fee>FEE</fee></root>' asXml() xpath('/root/fee') text()
assert value == 'FEE'
attribute¶
Retrieves the value of the specified attribute. A prefix:local name is resolved against the carried namespace context (see Namespaces); an optional default is returned when the attribute is absent.
Syntax
StringWrapper wrap.attribute(String name)
StringWrapper wrap.attribute(String name, String defaultValue)
Example
final String value = p6.wrap.of '<root attr="value"><fee>Fee</fee><foo>Foo</foo></root>' asXml() attribute('attr')
assert value == 'value'
final String value = p6.wrap.of '<root><fee bar="bee">Fee</fee><foo>Foo</foo></root>' asXml() xpath('/root/fee') attribute('bar')
assert value == 'bee'
final String value = p6.wrap.of '<root/>' asXml() attribute('missing', 'fallback')
assert value == 'fallback'
attributes¶
New Feature
Since 6.10.24
Returns the wrapped element’s attributes as a name -> value map.
Syntax
Map wrap.attributes()
Example
final attributes = p6.wrap.of '<root id="42" type="x"/>' asXml() attributes()
assert attributes.id == '42'
assert attributes.type == 'x'
hasAttribute¶
New Feature
Since 6.10.24
Returns true if the wrapped element carries the given attribute. Unlike attribute(name), this distinguishes an absent attribute from one set to an empty value.
Syntax
boolean wrap.hasAttribute(String name)
Example
final value = p6.wrap.of '<root id="42"/>' asXml() hasAttribute('id')
assert value == true
children¶
New Feature
Since 6.10.24
Returns the direct child elements as an ArrayWrapper of XmlWrapper.
Syntax
ArrayWrapper wrap.children()
Example
final children = p6.wrap.of '<root><a/><b/></root>' asXml() children()
assert children.size() == 2
Querying & navigation¶
xpath¶
Evaluates the specified xpath expression and returns the first matching node as a XmlWrapper.
Syntax
XmlWrapper wrap.xpath(String xpath)
Example
final w = p6.wrap.of '<root><fee bar="bee">Fee</fee><foo>Foo</foo></root>' asXml() xpath('/root/fee')
assert w instanceof XmlWrapper
list¶
Evaluates the specified xpath expression and returns all matching nodes as an ArrayWrapper of XmlWrapper.
Syntax
ArrayWrapper wrap.list(String xpath)
Example
final list = p6.wrap.of '<root><fee>Fee</fee><fee>Foo</fee></root>' asXml() list('/root/fee')
assert list.size() == 2
assert list.get(0).toString() == 'Fee'
assert list.get(1).toString() == 'Foo'
Aggregating list values
list returns element wrappers, so sum() on them directly would not work. Pull the text
out with native Groovy collect, then re-wrap the values to aggregate and format — a common
pattern over (namespaced) documents:
final ubl = p6.wrap.of(transaction.content).asXml().ignoringNamespaces()
final total = p6.wrap.of(
ubl.list('/Invoice/InvoiceLine/LineExtensionAmount').collect { it.text().get() }
).sum().round(2).format('#,##0.00')
exists¶
Checks if the specified xpath expression returns at least one node.
Syntax
boolean wrap.exists(String xpath)
Example
final exists = p6.wrap.of '<root><fee bar="bee">Fee</fee><foo>Foo</foo></root>' asXml() exists('/root/fee')
assert exists
value¶
New Feature
Since 6.10.24
Returns the text content of the first node matching the xpath, or an empty string when nothing matches. A null-safe alternative to xpath(x).toString().
Syntax
StringWrapper wrap.value(String xpath)
Example
final String value = p6.wrap.of '<root><fee>FEE</fee></root>' asXml() value('/root/fee')
assert value == 'FEE'
final String missing = p6.wrap.of '<root><fee>FEE</fee></root>' asXml() value('/root/missing')
assert missing == ''
count¶
New Feature
Since 6.10.24
Returns the number of nodes matching the xpath expression.
Syntax
NumberWrapper wrap.count(String xpath)
Example
final value = p6.wrap.of '<root><fee/><fee/><foo/></root>' asXml() count('/root/fee') get()
assert value == 2
parent¶
New Feature
Since 6.10.24
Returns the parent element, or null when there is none.
Warning
A wrapper produced by a copy-on-write edit (e.g. withText, append) is a detached root, so its parent() is null. Navigate from a wrapper obtained via xpath/list to walk upwards.
Syntax
XmlWrapper wrap.parent()
Example
final String value = p6.wrap.of '<root><fee>FEE</fee></root>' asXml() xpath('/root/fee') parent() name()
assert value == 'root'
root¶
New Feature
Since 6.10.24
Returns the root element of the document the wrapped element belongs to.
Syntax
XmlWrapper wrap.root()
Example
final String value = p6.wrap.of '<root><fee><bar/></fee></root>' asXml() xpath('//bar') root() name()
assert value == 'root'
withNamespaces¶
Namespaces
Since 6.10.24
To query or edit a namespaced document, call withNamespaces(Map) with a Map of prefix -> namespace URI. It returns a wrapper that remembers the mapping, propagates it to wrappers obtained by navigation, and applies it to every XPath-based query and edit (xpath, list, exists, replace, setText, setAttribute, remove) — so you set it once.
Syntax
XmlWrapper wrap.withNamespaces(Map namespaces)
Example
final doc = p6.wrap.of '<r:root xmlns:r="urn:x"><r:head/><r:fee>FEE</r:fee></r:root>' asXml() withNamespaces([r: 'urn:x'])
assert doc.xpath('/r:root/r:fee').toString() == 'FEE' // query
final String value = doc.remove('/r:root/r:head') source() // edit uses the same context
ignoringNamespaces¶
New Feature
Since 6.10.24
Returns a wrapper whose XPath queries and edits match element steps by local name,
regardless of namespace URI — so unprefixed paths match namespaced elements without binding
prefixes. Ideal for config-driven paths over documents like UBL (cac:/cbc:). Propagated to
wrappers obtained by navigation; edits keep the original namespaces in the output.
Note
Scope is element name-tests. Prefixed attribute tests (e.g. @ns:attr) still require
withNamespaces(Map).
Syntax
XmlWrapper wrap.ignoringNamespaces()
Example
final doc = p6.wrap.of('<r:root xmlns:r="urn:x"><r:fee>FEE</r:fee><r:tmp/></r:root>') asXml() ignoringNamespaces()
assert doc.value('/root/fee') == 'FEE' // matched despite the r: namespace
assert doc.setText('/root/fee', 'X').source().contains('r:fee') // edits preserve namespaces
assert !doc.remove('/root/tmp').exists('/root/tmp') // remove works prefix-free too
Output¶
source¶
Retrieves the xml source of the wrapped value (without the XML declaration). CDATA sections are preserved.
Syntax
StringWrapper wrap.source()
Example
final String value = p6.wrap.of '<root><fee>Fee</fee><foo>Foo</foo></root>' asXml() xpath('/root/fee') source()
assert value == '<fee>Fee</fee>'
sourceWithDeclaration¶
New Feature
Since 6.10.24
Retrieves the xml source prefixed with the XML declaration.
Syntax
StringWrapper wrap.sourceWithDeclaration()
Example
final String value = p6.wrap.of '<root/>' asXml() sourceWithDeclaration()
// <?xml version="1.0" encoding="UTF-8"?><root/>
prettySource¶
New Feature
Since 6.10.24
Returns the element serialized as pretty-printed (indented) XML. CDATA sections are preserved.
Syntax
StringWrapper wrap.prettySource()
Example
final String value = p6.wrap.of '<root><fee>FEE</fee></root>' asXml() prettySource()
asGPathResult¶
Retrieves the wrapped org.w3c.dom.Element and convert it to a GPathResult.
Syntax
GPathResult wrap.asGPathResult()
Example
final value = p6.wrap.of '<root><fee>Fee</fee><foo>Foo</foo></root>' asXml() xpath('/root/fee') asGPathResult()
asJson¶
New Feature
Since 6.10.24
Converts the wrapped XML to its JSON representation (a Map/List tree). Navigate the
result with native Groovy (GPath).
Syntax
Object wrap.asJson()
Example
final json = p6.wrap.of '<root><fee>FEE</fee></root>' asXml() asJson()
assert json.fee == 'FEE'
Editing (immutable)¶
Immutability
Since 6.10.24
The following methods (withAttribute, withAttributes, withoutAttribute, withText, withCdata, normalize, append, prepend, addChild, appendText, insertBefore, insertAfter, replace, rename, setText, setAttribute, removeAttribute and remove) never modify the wrapper they are called on. Each returns a new XmlWrapper holding an independent copy of the document, so the source wrapper is always left unchanged.
withAttribute¶
New Feature
Since 6.10.24
Returns a new XmlWrapper with the specified attribute set (or replaced).
Syntax
XmlWrapper wrap.withAttribute(String name, String value)
Example
final String value = p6.wrap.element('root').withAttribute('id', '42') source()
assert value == '<root id="42"/>'
withAttributes¶
New Feature
Since 6.10.24
Returns a new XmlWrapper with all of the given attributes set (or replaced) in one call.
Syntax
XmlWrapper wrap.withAttributes(Map attributes)
Example
final w = p6.wrap.element('root').withAttributes([id: '42', type: 'x'])
assert w.attribute('id').get() == '42'
assert w.attribute('type').get() == 'x'
withoutAttribute¶
New Feature
Since 6.10.24
Returns a new XmlWrapper with the specified attribute removed.
Syntax
XmlWrapper wrap.withoutAttribute(String name)
Example
final String value = p6.wrap.of '<root id="42" keep="yes"/>' asXml() withoutAttribute('id') source()
assert value == '<root keep="yes"/>'
withText¶
New Feature
Since 6.10.24
Returns a new XmlWrapper whose element has the specified text as its sole content, replacing any existing children.
Syntax
XmlWrapper wrap.withText(String text)
Example
final String value = p6.wrap.of '<root><old/></root>' asXml() withText('hello') source()
assert value == '<root>hello</root>'
withCdata¶
New Feature
Since 6.10.24
Returns a new XmlWrapper whose element holds the given text in a CDATA section as its sole content, replacing any existing children.
Syntax
XmlWrapper wrap.withCdata(String text)
Example
final String value = p6.wrap.of '<root>old</root>' asXml() withCdata('a<b') source()
assert value == '<root><![CDATA[a<b]]></root>'
normalize¶
New Feature
Since 6.10.24
Returns a new XmlWrapper with insignificant whitespace-only text nodes removed and adjacent text nodes merged.
Syntax
XmlWrapper wrap.normalize()
Example
final String value = p6.wrap.of '<root>\n <a/>\n <b/>\n</root>' asXml() normalize() source()
assert value == '<root><a/><b/></root>'
append¶
New Feature
Since 6.10.24
Returns a new XmlWrapper with the given child appended as the last child. The child can be another XmlWrapper or an XML fragment string (which may contain several top-level nodes).
Syntax
XmlWrapper wrap.append(XmlWrapper child)
XmlWrapper wrap.append(String xml)
Example
final String value = p6.wrap.element('root').append('<child>value</child>') source()
assert value == '<root><child>value</child></root>'
final String fragment = p6.wrap.element('root').append('<a/><b/>') source()
assert fragment == '<root><a/><b/></root>'
prepend¶
New Feature
Since 6.10.24
Returns a new XmlWrapper with the given child inserted as the first child. The child can be another XmlWrapper or an XML fragment string (which may contain several top-level nodes, inserted in order).
Syntax
XmlWrapper wrap.prepend(XmlWrapper child)
XmlWrapper wrap.prepend(String xml)
Example
final String value = p6.wrap.of '<root><b/></root>' asXml() prepend('<a/>') source()
assert value == '<root><a/><b/></root>'
addChild¶
New Feature
Since 6.10.24
Returns a new XmlWrapper with a new child element of the given name appended, optionally with a text value. A concise alternative to append(p6.wrap.element(...)).
Syntax
XmlWrapper wrap.addChild(String name)
XmlWrapper wrap.addChild(String name, String value)
Example
final String value = p6.wrap.element('root').addChild('fee').addChild('foo', 'FOO') source()
assert value == '<root><fee/><foo>FOO</foo></root>'
addChildNs¶
New Feature
Since 6.10.24
Returns a new XmlWrapper with a new namespaced child element appended, optionally with a
text value — the concise form of append(p6.wrap.elementNs(...)).
Syntax
XmlWrapper wrap.addChildNs(String qualifiedName, String namespaceUri)
XmlWrapper wrap.addChildNs(String qualifiedName, String namespaceUri, String value)
Example
final cbc = 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2'
final value = p6.wrap.elementNs('cac:Ref', 'urn:oasis:cac')
.addChildNs('cbc:ID', cbc, '42')
.source()
appendText¶
New Feature
Since 6.10.24
Returns a new XmlWrapper with the given text appended after any existing children, leaving them in place (unlike withText, which replaces all content).
Syntax
XmlWrapper wrap.appendText(String text)
Example
final String value = p6.wrap.of '<root><fee/></root>' asXml() appendText('tail') source()
assert value == '<root><fee/>tail</root>'
insertBefore¶
New Feature
Since 6.10.24
Returns a new XmlWrapper with the given node inserted immediately before every node matching the xpath. The node can be an XmlWrapper or an XML fragment string.
Syntax
XmlWrapper wrap.insertBefore(String xpath, XmlWrapper node)
XmlWrapper wrap.insertBefore(String xpath, String xml)
Example
final String value = p6.wrap.of '<root><b/></root>' asXml() insertBefore('/root/b', '<a/>') source()
assert value == '<root><a/><b/></root>'
insertAfter¶
New Feature
Since 6.10.24
Returns a new XmlWrapper with the given node inserted immediately after every node matching the xpath. The node can be an XmlWrapper or an XML fragment string.
Syntax
XmlWrapper wrap.insertAfter(String xpath, XmlWrapper node)
XmlWrapper wrap.insertAfter(String xpath, String xml)
Example
final String value = p6.wrap.of '<root><a/></root>' asXml() insertAfter('/root/a', '<b/>') source()
assert value == '<root><a/><b/></root>'
replace¶
New Feature
Since 6.10.24
Returns a new XmlWrapper with every node matching the xpath replaced by a copy of the given element.
Syntax
XmlWrapper wrap.replace(String xpath, XmlWrapper replacement)
Example
final String value = p6.wrap.of '<root><old/></root>' asXml() replace('/root/old', p6.wrap.element('new', 'x')) source()
assert value == '<root><new>x</new></root>'
rename¶
New Feature
Since 6.10.24
Returns a new XmlWrapper with the root element renamed.
Syntax
XmlWrapper wrap.rename(String name)
Example
final String value = p6.wrap.of '<root id="1"/>' asXml() rename('renamed') source()
assert value == '<renamed id="1"/>'
setText¶
New Feature
Since 6.10.24
Returns a new XmlWrapper where the text content of every node matching the xpath is set to the given value.
Syntax
XmlWrapper wrap.setText(String xpath, String value)
Example
final String value = p6.wrap.of '<root><fee>old</fee></root>' asXml() setText('/root/fee', 'new') source()
assert value == '<root><fee>new</fee></root>'
setAttribute¶
New Feature
Since 6.10.24
Returns a new XmlWrapper where the given attribute is set on every element matching the xpath.
Syntax
XmlWrapper wrap.setAttribute(String xpath, String name, String value)
Example
final String value = p6.wrap.of '<root><fee/></root>' asXml() setAttribute('/root/fee', 'id', '7') source()
assert value == '<root><fee id="7"/></root>'
removeAttribute¶
New Feature
Since 6.10.24
Returns a new XmlWrapper where the given attribute is removed from every element matching the xpath.
Syntax
XmlWrapper wrap.removeAttribute(String xpath, String name)
Example
final String value = p6.wrap.of '<root><fee id="1" keep="x"/></root>' asXml() removeAttribute('/root/fee', 'id') source()
assert value == '<root><fee keep="x"/></root>'
remove¶
New Feature
Since 6.10.24
Returns a new XmlWrapper with every node matching the xpath expression removed.
Syntax
XmlWrapper wrap.remove(String xpath)
Example
final String value = p6.wrap.of '<root><a/><b/><a/></root>' asXml() remove('/root/a') source()
assert value == '<root><b/></root>'
Note
The xpath-based edits (replace, setText, setAttribute, removeAttribute, remove, insertBefore, insertAfter) resolve prefixes against the carried namespace context — set it with withNamespaces(Map) before editing a namespaced document (see the Namespaces note above).
Validation & transform¶
validate¶
New Feature
Since 6.10.24
Validates the wrapped element against the given XSD schema. Returns the same wrapper unchanged when valid, throws otherwise.
Syntax
XmlWrapper wrap.validate(String xsd)
Example
final xsd = '<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"><xs:element name="age" type="xs:int"/></xs:schema>'
p6.wrap.of '<age>30</age>' asXml() validate(xsd)
transform¶
New Feature
Since 6.10.24
Applies the given XSLT stylesheet (Saxon, XSLT 2.0/3.0 capable) to the wrapped element and returns the result as a new XmlWrapper. The current wrapper is left unchanged.
Syntax
XmlWrapper wrap.transform(String xslt)
Example
final xslt = '<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"><xsl:template match="/root"><out><xsl:value-of select="."/></out></xsl:template></xsl:stylesheet>'
final String value = p6.wrap.of '<root>hello</root>' asXml() transform(xslt) source()
assert value == '<out>hello</out>'
Complete example¶
This end-to-end walkthrough combines most of the XmlWrapper methods — building, reading, navigating, editing (all immutable), namespaces, output, validation and transformation. Each step builds on the previous one.
1. Build a document from scratch¶
def order = p6.wrap.element('order')
.withAttribute('id', '1001')
.addChild('customer', 'ACME')
.addChild('total', '0.00')
assert order.source().get() == '<order id="1001"><customer>ACME</customer><total>0.00</total></order>'
2. Grow the tree¶
order = order
.append('<lines><line sku="A1" qty="2"/><line sku="B7" qty="1"/></lines>')
.insertBefore('/order/customer', '<channel>web</channel>')
assert order.children().size().get() == 4 // channel, customer, total, lines
3. Read & navigate¶
assert order.name().get() == 'order'
assert order.attribute('id').get() == '1001'
assert order.attribute('missing', 'n/a').get() == 'n/a'
assert order.value('/order/customer') == 'ACME'
assert order.exists('/order/channel')
assert order.count('/order/lines/line').get() == 2
def firstLine = order.xpath('/order/lines/line')
assert firstLine.attribute('sku').get() == 'A1'
assert firstLine.text().get() == ''
assert firstLine.parent().name().get() == 'lines' // upward navigation
assert firstLine.root().name().get() == 'order'
4. Edit immutably¶
def updated = order
.withAttributes([currency: 'EUR', status: 'NEW'])
.setText('/order/total', '199.00')
.setAttribute('/order/lines/line[@sku="A1"]', 'qty', '3')
.removeAttribute('/order/lines/line[@sku="B7"]', 'qty')
.replace('/order/channel', p6.wrap.element('source', 'portal'))
.rename('purchaseOrder')
assert updated.name().get() == 'purchaseOrder'
assert updated.value('/purchaseOrder/total') == '199.00'
assert updated.attribute('status').get() == 'NEW'
assert updated.xpath('/purchaseOrder/lines/line[@sku="A1"]').attribute('qty').get() == '3'
assert !updated.xpath('/purchaseOrder/lines/line[@sku="B7"]').hasAttribute('qty')
assert updated.exists('/purchaseOrder/source')
5. Query & edit a namespaced document¶
def soap = p6.wrap.of('''<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header/>
<s:Body><getPrice/></s:Body>
</s:Envelope>''').asXml().withNamespaces([s: 'http://schemas.xmlsoap.org/soap/envelope/'])
assert soap.exists('/s:Envelope/s:Body')
def body = soap.remove('/s:Envelope/s:Header').normalize()
assert !body.exists('/s:Envelope/s:Header')
// alternatively, match element steps by local name — no prefixes to bind (ideal for UBL):
def anyNs = soap.ignoringNamespaces()
assert anyNs.exists('/Envelope/Body/getPrice')
assert anyNs.count('/Envelope/Body').get() == 1
6. Embed raw content as CDATA¶
def note = p6.wrap.element('note').withCdata('1 < 2 && 3 > 2')
assert note.source().get() == '<note><![CDATA[1 < 2 && 3 > 2]]></note>'
7. Output forms¶
println updated.prettySource().get() // indented
println updated.sourceWithDeclaration().get() // prefixed with <?xml ...?>
def json = updated.asJson() // JSON view of the XML
def gpath = updated.asGPathResult() // Groovy GPathResult
8. Validate against an XSD¶
def xsd = '<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"><xs:element name="age" type="xs:int"/></xs:schema>'
p6.wrap.of('<age>30</age>').asXml().validate(xsd)
9. Transform with XSLT¶
def xslt = '''<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="/purchaseOrder"><summary lines="{count(//line)}"/></xsl:template>
</xsl:stylesheet>'''
def summary = updated.transform(xslt)
assert summary.name().get() == 'summary'
assert summary.attribute('lines').get() == '2'