Skip to content

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 toString method 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'