"Working with traditional Java APIs and libraries to create and parse XML documents tends to lower my spirits. And naigating the document hierarchy using the DOM API is one sure way to drive me insane."
Można się z tym zgodzić bądź nie, ale pochylić się nad tym i rozważyć rozwiązania alternatywne zdecydowanie warto. Może się w końcu okazać, że będziemy mieli więcej czasu na inne czynności niż obsługa XMLi.
Na początek omawiana jest klasa DOMCategory, która korzysta z DOM do obsługi XML. Klasa korzysta z mechanizmu kategorii w Groovy, które pozwalają definiować nowe metody w klasach bez ich modyfikacji (!) DOMCategory dodaje metody tak, że nawigacja po strukturze DOM sprowadza się do odczytu atrybutów klasy (która de facto tych atrybutów nie ma, ale właśnie z DOMCategory mamy wrażenie, jakby miała). Atrybuty dostępne są za pomocą konstrukcji @<nazwa-atrybutu>. Podobnie jak XPath służy do nawigacji po XML, tak GPath umożliwia nawigację po hierarchii POJO i POGO oraz XML. Przykład powinien rozwiać wszelkie wątpliwości.
groovy:000> f = File.createTempFile("dane", ".xml")W ramach use korzystałem z atrybutów serwer, dostawca i @nazwa, jakby były one zdefiniowane w ramach odpowiednich typów. Wszystko za sprawą konstrukcji use (więcej o tym w kolejnym wpisie, niebawem).
===> c:\temp\dane7020472904153694958.xml
groovy:000> tresc = """<serwery>
groovy:001> <serwer nazwa="Apache Geronimo">
groovy:002> <dostawca>Apache Software Foundation</dostawca>
groovy:003> </serwer>
groovy:004> <serwer nazwa="JBoss Application Server">
groovy:005> <dostawca>JBoss.org</dostawca>
groovy:006> </serwer>
groovy:007> </serwery>"""
===> <serwery>
<serwer nazwa="Apache Geronimo">
<dostawca>Apache Software Foundation</dostawca>
</serwer>
<serwer nazwa="JBoss Application Server">
<dostawca>JBoss.org</dostawca>
</serwer>
</serwery>
groovy:000> f << tresc
===> c:\temp\dane7020472904153694958.xml
groovy:000> f.text
===> <serwery>
<serwer nazwa="Apache Geronimo">
<dostawca>Apache Software Foundation</dostawca>
</serwer>
<serwer nazwa="JBoss Application Server">
<dostawca>JBoss.org</dostawca>
</serwer>
</serwery>
groovy:000> doc = groovy.xml.DOMBuilder.parse(new FileReader(f))
===> [#document: null]
groovy:000> rootElement = doc.documentElement
===> [serwery: null]
groovy:000> use(groovy.xml.dom.DOMCategory) {
groovy:001> println "Serwery i ich dostawcy"
groovy:002> serwery = rootElement.serwer
groovy:003> serwery.each { serwer -> println "${serwer.'@nazwa'} dostarczany jest przez ${serwer.dostawca[0].text()}" }
groovy:004> }
Serwery i ich dostawcy
Apache Geronimo dostarczany jest przez Apache Software Foundation
JBoss Application Server dostarczany jest przez JBoss.org
===> groovy.xml.dom.DOMCategory$NodesHolder@6a2f81
Kolejnym podejściem do obsługi plików XML jest wykorzystanie groovy.util.XMLParser. Podobnie działa jak DOMCategory, aczkolwiek nie nakłada obowiązku użycia bloku use.
groovy:000> f = File.createTempFile("dane", ".xml")Brak użycia bloku use oraz kilka atrakcji w stylu each(), collect(), czy find() dla elementów xmlowych stanowi ciekawą alternatywę dla DOMCategory. W przypadku tego rozwiązania komentarze oraz instrukcje XML nie są zachowane.
===> c:\temp\dane483287696987389412.xml
groovy:000> tresc = """<serwery>
groovy:001> <serwer nazwa="Apache Geronimo">
groovy:002> <dostawca>Apache Software Foundation</dostawca>
groovy:003> </serwer>
groovy:004> <serwer nazwa="JBoss Application Server">
groovy:005> <dostawca>JBoss.org</dostawca>
groovy:006> </serwer>
groovy:007> </serwery>"""
===> <serwery>
<serwer nazwa="Apache Geronimo">
<dostawca>Apache Software Foundation</dostawca>
</serwer>
<serwer nazwa="JBoss Application Server">
<dostawca>JBoss.org</dostawca>
</serwer>
</serwery>
groovy:000> f << tresc
===> c:\temp\dane483287696987389412.xml
groovy:000> doc = new XmlParser().parse(f)
===> serwery[attributes={}; value=[serwer[attributes={nazwa=Apache Geronimo}; value=[dostawca[attributes={};
value=[Apache Software Foundation]]]], serwer[attributes={nazwa=JBoss Application Server};
value=[dostawca[attributes={}; value=[JBoss.org]]]]]]
groovy:000> println "Serwery i ich dostawcy"
Serwery i ich dostawcy
===> null
groovy:000> doc.each {
groovy:001> println "${it.@nazwa} dostarczany jest przez ${it.dostawca[0].text()}"
groovy:002> }
Apache Geronimo dostarczany jest przez Apache Software Foundation
JBoss Application Server dostarczany jest przez JBoss.org
===> serwery[attributes={}; value=[serwer[attributes={nazwa=Apache Geronimo}; value=[dostawca[attributes={};
value=[Apache Software Foundation]]]], serwer[attributes={nazwa=JBoss Application Server};
value=[dostawca[attributes={}; value=[JBoss.org]]]]]]
Użycie XmlParser wiąże się z dużym narzutem pamięciowym, więc kolejnym rozwiązaniem, znacznie mniej pamięciożernym, jest XmlSlurper. Użycie podobne do XmlParser z tym, że dodatkowo mamy obsługę przestrzeni nazewniczych w XML przez określenie ich przez metodę declareNamespace().
f = File.createTempFile("dane", ".xml")Tworzenie XMLi już widzieliśmy. Po prostu korzystamy z potrójnego cudzysłowu do utworzenia treści XML, w której rozwiązywane są wyrażenia Groovy.
f << tresc
tresc = """<p:serwery xmlns:p="Przyklad">
<p:serwer nazwa="Apache Geronimo">
<p:dostawca>Apache Software Foundation</p:dostawca>
</p:serwer>
</p:serwery>"""
doc = new XmlSlurper().parse(f).declareNamespace(pk: 'Przyklad')
println "${doc.'pk:serwer'.@nazwa} dostarczany przez ${doc.'pk:serwer'.dostawca[0].text()}"
groovy:000> serwery = ['Apache Geronimo':'Apache Software Foundation', 'JBoss AS':'JBoss.org']Można również skorzystać z MarkupBuilder lub StreamingMarkupBuilder. Podsumowując: "Groovy can make working with XML bearable" (str. 155). Czyż nie?
===> {Apache Geronimo=Apache Software Foundation, JBoss AS=JBoss.org}
groovy:000> tresc = ''
===>
groovy:000> serwery.each { serwer, dostawca ->
groovy:001> wycinek = """
groovy:002> <serwer nazwa="${serwer}">
groovy:003> <dostawca>${dostawca}</dostawca>
groovy:004> </serwer>
groovy:005> """
groovy:006>
groovy:006> tresc += wycinek
groovy:007> }
===> {Apache Geronimo=Apache Software Foundation, JBoss AS=JBoss.org}
groovy:000> doc = "<serwery>${tresc}</serwery>"
===> <serwery>
<serwer nazwa="Apache Geronimo">
<dostawca>Apache Software Foundation</dostawca>
</serwer>
<serwer nazwa="JBoss AS">
<dostawca>JBoss.org</dostawca>
</serwer>
</serwery>
groovy:000>
groovy:000> println doc
<serwery>
<serwer nazwa="Apache Geronimo">
<dostawca>Apache Software Foundation</dostawca>
</serwer>
<serwer nazwa="JBoss AS">
<dostawca>JBoss.org</dostawca>
</serwer>
</serwery>
===> null
1) Tak groovy jest fajny, sam go używam jak wiesz, ale nie można zapominać, że dość dużo z tego co da się zrobić w groovy, da się osiągnąć w porządnie zaprojektowanym języku statycznie typowanym. Dlatego polecam spróbować Scala albo wręcz F# / Ocaml. Bo nie wszystko to zasługa dynamizmu!
OdpowiedzUsuń2) Sam stosowałem groovysh, ale wypomniano mi, że jest dużo mniej czytelne niż zrzuty z GroovyConsole i się z tym aktualnie zgodzę.
Poza tym szkoda, że nie dotrę na Javarsovia :/
Pozdrawiam,
Krzysztof Kowalczyk
A ja jeszcze dodam, że oprócz XMLa Groovy/Grails całkiem fajnie obsługują JSONa, zarówno przy zwracaniu danych (render kolekcja as JSON) ja i przy ich parsowaniu (JSON.parse(..) daje mapę)
OdpowiedzUsuń