XML processing utilities for Apache Groovy including markup builders, parsers, and navigation tools
—
Groovy XML provides comprehensive namespace support for creating, parsing, and navigating XML documents with proper namespace handling, including namespace-aware builders, parsers, and navigation tools.
Core class for managing XML namespaces and creating qualified names.
public class Namespace {
// Constructors
public Namespace();
public Namespace(String uri);
public Namespace(String uri, String prefix);
// Core methods
public QName get(String localName);
public String getPrefix();
public String getUri();
// Utility methods
@Override
public boolean equals(Object obj);
@Override
public int hashCode();
@Override
public String toString();
}// Create namespaces
def defaultNs = new Namespace('http://example.com/default')
def metaNs = new Namespace('http://example.com/metadata', 'meta')
def dataNs = new Namespace('http://example.com/data', 'data')
// Create qualified names
def titleQName = metaNs.get('title')
def itemQName = dataNs.get('item')
def rootQName = defaultNs.get('root')
println titleQName // {http://example.com/metadata}title
println itemQName // {http://example.com/data}item
// Use in builders
def xml = new MarkupBuilder(writer)
xml."${rootQName}" {
"${titleQName}"('Document Title')
"${itemQName}"(id: '1', 'Item content')
}QName class for representing qualified XML names with namespace information.
public class QName {
// Constructors
public QName(String localName);
public QName(String namespaceURI, String localName);
public QName(String namespaceURI, String localName, String prefix);
// Accessor methods
public String getLocalName();
public String getNamespaceURI();
public String getPrefix();
// Utility methods
@Override
public boolean equals(Object obj);
@Override
public int hashCode();
@Override
public String toString();
}// Create QNames directly
def qname1 = new QName('title')
def qname2 = new QName('http://example.com/ns', 'title')
def qname3 = new QName('http://example.com/ns', 'title', 'ns')
// Access QName properties
println qname3.localName // "title"
println qname3.namespaceURI // "http://example.com/ns"
println qname3.prefix // "ns"
// Use in XML operations
def builder = new MarkupBuilder(writer)
builder."${qname3}" {
content('Namespaced content')
}def xml = new MarkupBuilder(writer)
// Method 1: Explicit namespace declarations
xml.root(
'xmlns': 'http://example.com/default',
'xmlns:meta': 'http://example.com/metadata',
'xmlns:data': 'http://example.com/data'
) {
'meta:header' {
'meta:title'('Document Title')
'meta:created'(new Date().toString())
}
'data:content' {
'data:item'(id: '1') {
'data:name'('Item 1')
'data:value'('Value 1')
}
'data:item'(id: '2') {
'data:name'('Item 2')
'data:value'('Value 2')
}
}
}
// Method 2: Using Namespace objects
def defaultNs = new Namespace('http://example.com/default')
def metaNs = new Namespace('http://example.com/metadata', 'meta')
def dataNs = new Namespace('http://example.com/data', 'data')
xml."${defaultNs.get('document')}"(
'xmlns': defaultNs.uri,
'xmlns:meta': metaNs.uri,
'xmlns:data': dataNs.uri
) {
"${metaNs.get('metadata')}" {
"${metaNs.get('title')}"('Namespace Document')
}
"${dataNs.get('records')}" {
(1..3).each { i ->
"${dataNs.get('record')}"(id: i) {
"${dataNs.get('value')}"("Record ${i}")
}
}
}
}def smb = new StreamingMarkupBuilder()
def content = smb.bind {
mkp.xmlDeclaration(version: '1.0', encoding: 'UTF-8')
// Define namespace mappings
namespaces = [
'': 'http://example.com/default',
'meta': 'http://example.com/metadata',
'data': 'http://example.com/data',
'custom': 'http://example.com/custom'
]
document(
xmlns: namespaces[''],
'xmlns:meta': namespaces['meta'],
'xmlns:data': namespaces['data'],
'xmlns:custom': namespaces['custom']
) {
'meta:header' {
'meta:title'('Streaming Namespace Document')
'meta:generator'('Groovy StreamingMarkupBuilder')
}
'data:body' {
(1..100).each { i ->
'data:record'(id: i, 'custom:priority': i % 3) {
'data:content'("Content ${i}")
'custom:metadata' {
'custom:category'("Category ${i % 5}")
'custom:tags' {
(1..(i % 3 + 1)).each { j ->
'custom:tag'("tag${j}")
}
}
}
}
}
}
}
}
content.writeTo(new FileWriter('namespaced-streaming.xml'))// Create namespace-aware parser
def parser = new XmlParser(false, true) // not validating, namespace aware
def nsXml = '''<?xml version="1.0" encoding="UTF-8"?>
<root xmlns="http://example.com/default"
xmlns:meta="http://example.com/metadata"
xmlns:data="http://example.com/data">
<meta:header>
<meta:title>Sample Document</meta:title>
<meta:version>1.0</meta:version>
</meta:header>
<data:content>
<data:item id="1">
<data:name>Item 1</data:name>
<data:value>Value 1</data:value>
</data:item>
<data:item id="2">
<data:name>Item 2</data:name>
<data:value>Value 2</data:value>
</data:item>
</data:content>
</root>'''
def root = parser.parseText(nsXml)
// Access namespaced elements
println root.name() // Will include namespace information
def header = root['meta:header'][0]
def title = header['meta:title'][0].text()
def items = root['data:content'][0]['data:item']
items.each { item ->
def name = item['data:name'][0].text()
def value = item['data:value'][0].text()
println "Item: ${name} = ${value}"
}
// Check namespace information
def itemNode = items[0]
println "Namespace URI: ${itemNode.namespaceURI}"
println "Local name: ${itemNode.localName}"// Create namespace-aware slurper
def slurper = new XmlSlurper(false, true) // not validating, namespace aware
def nsDoc = slurper.parseText(nsXml)
// Navigate with namespace prefixes
println nsDoc.'meta:header'.'meta:title'.text()
println nsDoc.'data:content'.'data:item'.size()
// Access all items
nsDoc.'data:content'.'data:item'.each { item ->
println "ID: ${item.'@id'}"
println "Name: ${item.'data:name'.text()}"
println "Value: ${item.'data:value'.text()}"
}
// Declare namespace mappings for easier access
def mappedDoc = nsDoc.declareNamespace([
'default': 'http://example.com/default',
'm': 'http://example.com/metadata',
'd': 'http://example.com/data'
])
// Use shorter prefixes
println mappedDoc.'m:header'.'m:title'.text()
println mappedDoc.'d:content'.'d:item'.'d:name'.text()
// Look up namespace URIs
println nsDoc.lookupNamespace('meta') // "http://example.com/metadata"
println nsDoc.lookupNamespace('data') // "http://example.com/data"Builder with enhanced namespace support for complex namespace scenarios.
public class NamespaceBuilder extends NamespaceBuilderSupport {
// Constructors
public NamespaceBuilder();
public NamespaceBuilder(Writer writer);
// Namespace-aware building methods inherited from parent
}Base class providing namespace-aware building functionality.
public abstract class NamespaceBuilderSupport extends BuilderSupport {
// Namespace management
protected Map<String, String> namespaceMethodMap;
protected boolean autoPrefix;
// Configuration methods
public void setAutoPrefix(boolean autoPrefix);
public boolean getAutoPrefix();
// Namespace declaration methods
public void declareNamespace(String prefix, String namespaceURI);
public void declareNamespace(Map<String, String> namespaceMap);
}def nsBuilder = new NamespaceBuilder(writer)
// Configure automatic prefixing
nsBuilder.setAutoPrefix(true)
// Declare namespaces
nsBuilder.declareNamespace('meta', 'http://example.com/metadata')
nsBuilder.declareNamespace('data', 'http://example.com/data')
// Build with automatic namespace handling
nsBuilder.root {
'meta:information' {
'meta:title'('Auto-prefixed Document')
'meta:description'('Demonstrates automatic namespace handling')
}
'data:records' {
record(id: '1') {
'data:content'('Content 1')
}
record(id: '2') {
'data:content'('Content 2')
}
}
}def createNamespacedDocument = { namespaceMap, data ->
def smb = new StreamingMarkupBuilder()
smb.bind {
mkp.xmlDeclaration(version: '1.0', encoding: 'UTF-8')
// Create root with all namespace declarations
def rootAttrs = [:]
namespaceMap.each { prefix, uri ->
def attrName = prefix ? "xmlns:${prefix}" : 'xmlns'
rootAttrs[attrName] = uri
}
document(rootAttrs) {
// Use namespaces dynamically
data.each { section ->
def prefix = section.namespace ?: ''
def elementName = prefix ? "${prefix}:${section.name}" : section.name
"${elementName}"(section.attributes ?: [:]) {
section.children?.each { child ->
def childPrefix = child.namespace ?: ''
def childName = childPrefix ? "${childPrefix}:${child.name}" : child.name
"${childName}"(child.content)
}
}
}
}
}
}
// Usage
def namespaces = [
'': 'http://example.com/default',
'meta': 'http://example.com/metadata',
'data': 'http://example.com/data'
]
def documentData = [
[name: 'header', namespace: 'meta', children: [
[name: 'title', content: 'Dynamic Document'],
[name: 'version', content: '1.0']
]],
[name: 'content', namespace: 'data', children: [
[name: 'item', content: 'Item 1'],
[name: 'item', content: 'Item 2']
]]
]
def result = createNamespacedDocument(namespaces, documentData)
result.writeTo(new FileWriter('dynamic-namespaces.xml'))class NamespaceValidator {
static boolean validateNamespaces(GPathResult doc) {
def namespaces = [:]
// Collect all namespace declarations
doc.depthFirst().each { node ->
node.attributes().each { name, value ->
if (name.startsWith('xmlns')) {
def prefix = name == 'xmlns' ? '' : name.substring(6)
namespaces[prefix] = value
}
}
}
// Validate all prefixed elements have declarations
def valid = true
doc.depthFirst().each { node ->
def nodeName = node.name()
if (nodeName.contains(':')) {
def prefix = nodeName.split(':')[0]
if (!namespaces.containsKey(prefix)) {
println "Undeclared namespace prefix: ${prefix}"
valid = false
}
}
}
return valid
}
static Map<String, String> extractNamespaces(GPathResult doc) {
def namespaces = [:]
doc.depthFirst().each { node ->
node.attributes().each { name, value ->
if (name.startsWith('xmlns')) {
def prefix = name == 'xmlns' ? '' : name.substring(6)
namespaces[prefix] = value
}
}
}
return namespaces
}
}
// Usage
def slurper = new XmlSlurper(false, true)
def doc = slurper.parseText(namespacedXml)
def isValid = NamespaceValidator.validateNamespaces(doc)
def namespaces = NamespaceValidator.extractNamespaces(doc)
println "Document valid: ${isValid}"
println "Namespaces: ${namespaces}"def transformWithNamespaces = { inputXml, transformationRules ->
def slurper = new XmlSlurper(false, true)
def doc = slurper.parseText(inputXml)
def smb = new StreamingMarkupBuilder()
def result = smb.bind {
mkp.xmlDeclaration(version: '1.0', encoding: 'UTF-8')
// Extract and preserve namespaces
def nsDeclarations = [:]
doc.attributes().each { name, value ->
if (name.toString().startsWith('xmlns')) {
nsDeclarations[name] = value
}
}
transformedDocument(nsDeclarations) {
// Apply transformation rules while preserving namespaces
doc.children().each { child ->
def childName = child.name()
def rule = transformationRules[childName]
if (rule) {
"${rule.newName ?: childName}"(rule.newAttributes ?: [:]) {
if (rule.transform) {
rule.transform(child)
} else {
// Copy child content preserving namespaces
child.children().each { grandchild ->
"${grandchild.name()}"(grandchild.text())
}
}
}
}
}
}
}
return result
}
// Usage
def rules = [
'meta:header': [
newName: 'header',
transform: { node ->
'title'(node.'meta:title'.text().toUpperCase())
'processed'(new Date().toString())
}
],
'data:content': [
newName: 'processedData',
transform: { node ->
node.'data:item'.each { item ->
processedItem(id: item.'@id') {
originalName(item.'data:name'.text())
processedValue(item.'data:value'.text().reverse())
}
}
}
]
]
def transformed = transformWithNamespaces(namespacedXml, rules)
transformed.writeTo(new FileWriter('transformed.xml'))class NamespaceBestPractices {
// Always use meaningful namespace URIs
static final Map<String, String> STANDARD_NAMESPACES = [
'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
'xs': 'http://www.w3.org/2001/XMLSchema',
'xml': 'http://www.w3.org/XML/1998/namespace'
]
static void declareStandardNamespaces(MarkupBuilder builder) {
// Always declare standard namespaces when needed
STANDARD_NAMESPACES.each { prefix, uri ->
builder."xmlns:${prefix}" = uri
}
}
static String createNamespaceURI(String organization, String component, String version = null) {
def uri = "http://${organization}/schemas/${component}"
return version ? "${uri}/${version}" : uri
}
static boolean isValidNamespaceURI(String uri) {
// Basic validation for namespace URIs
return uri ==~ /^https?:\/\/.+/ || uri ==~ /^urn:.+/
}
static void validateAndBuild(Map<String, String> namespaces, Closure xmlContent) {
// Validate all namespace URIs before building
namespaces.each { prefix, uri ->
if (!isValidNamespaceURI(uri)) {
throw new IllegalArgumentException("Invalid namespace URI: ${uri}")
}
}
def smb = new StreamingMarkupBuilder()
def result = smb.bind {
mkp.xmlDeclaration(version: '1.0', encoding: 'UTF-8')
def rootAttrs = [:]
namespaces.each { prefix, uri ->
def attrName = prefix ? "xmlns:${prefix}" : 'xmlns'
rootAttrs[attrName] = uri
}
document(rootAttrs, xmlContent)
}
return result
}
}Install with Tessl CLI
npx tessl i tessl/maven-org-codehaus-groovy--groovy-xml