Причёсывание XML с помощью Groovy
Дано:
- неиспользуемые нэймспэйсы
- дублирование нэймспэйсов
- нэймспэйсы раскиданы по разным элементам
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:unused="http://unused.ns">
<soapenv:Header/>
<soapenv:Body>
<ns1:GetWeather xmlns:ns1="http://weather">
<ns2:CityName xmlns:ns2="http://help.weather">Vladimir</ns2:CityName>
<ns2a:CountryName xmlns:ns2a="http://help.weather">Russian Federation</ns2a:CountryName>
</ns1:GetWeather>
</soapenv:Body>
</soapenv:Envelope>
Хотим получить:
- все нэймспэйсы перечислены в корневом элементе
- отсутствуют неиспользованные
- отсутствует дублирование
- алиасы заменены на “красивые”
<e:Envelope
xmlns:e="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:w="http://weather"
xmlns:h="http://help.weather">
<e:Header/>
<e:Body>
<w:GetWeather>
<h:CityName>Vladimir</h:CityName>
<h:CountryName>Russian Federation</h:CountryName>
</w:GetWeather>
</e:Body>
</e:Envelope>
Воспользуемся встроенным в groovy xml-парсером (XmlSlurper)
String xmlString = new File('soap1.xml').text
GPathResult xml = new XmlSlurper().parseText(xmlString)
Вытащим все нэймспэйсы из xml элементов и уберем дубликаты (такой способ не подойдет если используются атрибуты с нэймспэйсами)
List<String> namespaces = xml.'**'.collect { it.namespaceURI() }.unique()
Если в xml используются атрибуты с нэймспэйсами, их можно вытащить так:
List<String> namespaces = xml.'**'.inject([]) { list, el ->
list += el.namespaceURI()
list += el.attributes()*.key
.findAll { it.startsWith("{") }
.collect { it[1..it.indexOf("}")-1] }
}.unique().minus('')
Убедимся, что в списке нет неиспользуемых нэймспэйсов (xmlns:unused=”http://unused.ns”)
assert ['http://schemas.xmlsoap.org/soap/envelope/',
'http://weather',
'http://help.weather'] == namespaces
Создадим карту соответствия namespace:alias
def prettyNs = ['http://schemas.xmlsoap.org/soap/envelope/': 'e',
'http://weather' : 'w',
'http://help.weather' : 'h',
'http://unused.ns' : 'unused']
Строим красивый XML и записываем в строку prettyXml
String prettyXml = XmlUtil.serialize(new StreamingMarkupBuilder().bind {
prettyNs.findAll { namespaces.contains(it.key) }.each { ns ->
mkp.declareNamespace((ns.value): ns.key)
}
mkp.yield xml
})
Готово. Такой xml не стыдно добавить в документацию или еще куда нибудь.
Откуда берется xml в котором нэймспэйсы не в корне?
XML, в котором нэймспэйсы дублируются и разбросаны по нодам получается при вставке элементов в существующий документ. Например, в том же groovy это можно сделать так:
String xmlString = '''\
<ns1:a xmlns:ns1="http://ns1" xmlns:ns2="http://ns2" xmlns:ns3="http://ns3">
<ns2:b>
<ns3:c>Test</ns3:c>
</ns2:b>
</ns1:a>
'''
def xml = new XmlSlurper().parseText(xmlString)
xml.b.c.replaceNode {
def newElement = '<ns10:x xmlns:ns10="http://ns10">10</ns10:x>'
mkp.yield new XmlSlurper().parseText(newElement)
}
def actual = XmlUtil.serialize(xml)
def expected = XmlUtil.serialize('''\
<ns1:a xmlns:ns1="http://ns1">
<ns2:b xmlns:ns2="http://ns2">
<ns10:x xmlns:ns10="http://ns10">10</ns10:x>
</ns2:b>
</ns1:a>''')
assert actual == expected
В groovy очень удобно парсить и менять xml. Еще он должен хорошо справляться с огромными документами, так как XmlSlurper и XmlParser основаны на SAX и используют мало памяти.