Čím nahradit Scala.XML
Ve Scale je možné v programu zapisovat přímo XML literály. Ještě pořád, ale už ne příliš dlouho. Martin Odersky (a jeho gang) není jejich fanda, už roky se jich chce zbavit a ve verzi 2.13 se mu to možná konečně podaří.
Technicky vzato XML literály nejsou třeba. Jazyk od verze 2.10 disponuje interpolace stringů, která dokáže tuto funkcionalitu beze zbytku nahradit. S ní bychom psali místo
val t = <tag>body</tag>
o něco málo delší
val t = xml"<tag>body</tag>"
Když padne vestavěné XML, parser se zjednoduší, XML v něm nebude mít výsadní postavení a nebude vázané na neohrabané API scala.xml. Půjde jen o další z mnoha uživatelských knihoven. Z těchto důvodů bychom s ním neměli dlouhodobě počítat. Čím ho tedy můžeme nahradit?
XML literály jsou často používané na generování RSS, Atomu a jiných XML výstupů. Vytvoří se DOM, pak je okamžitě přeměněn na řetězec a zahozen.
(<rss version="2.0"> <channel> <title>{blog.title}</title>{ articles.map(a => <item> <title>{a.title}</title> <guid isPermaLink="true">{urlOf(a)}</guid> <pubDate>{rssDate(a.date)}</pubDate> <description>{mkBody(a)}</description> </item>) }</channel> </rss>).toString
To vypadá přehledně, ale má to jeden nedostatek: Postaví se kompletní DOM se všemi alokacemi jen proto, aby byl okamžitě zahozen. V případě generování výstupů bude stačit něco jednoduššího. Nepotřebuji reprezentaci XML stromu, nepotřebuji ho zkoumat a měnit, stačí jen přímo vypsat jeho obsah. Pokud možno přehledně.
Tento účel splní starý dobrý XMLStreamWriter zabalený do přehledného API. Vytvoření stejného RSS dokumentu pak může vypadat nějak takhle:
XMLSW.document { w => w.element("rss", Seq("version" -> "2.0")) { w => w.element("channel") { w => w.element("title", blog.title) for (a <- articles) { w.element("item") { w => w.element("title", a.title) w.element("guid", Seq(("isPermaLink", "true")), urlOf(a)) w.element("pubDate", rssDate(a.date)) w.element("description", makeBody(a)) } } } } }
Není třeba volat writeStartElement
a pak odpovídající writeEndElement
, tak
aby se nekřížily, lexikální blok se o postará o uzavírání tagů a jejich
vnořování. Výsledek je měřitelně rychlejší než generování pomocí scala.xml, ale
ukázalo se, že čísla jsou nestálá a někdy divoce skáčou.
Příčinu odhalí pohled do zdrojáků. Z nich je vidět, že XMLStreamWriter
je
(jako mnoho javovských API) určený pro znovupoužití, ne jako jednoduchý
nástroj, který se použije jednou a pak je zahozen. Interní implementace alokuje
mnoho objektů, některé z nich nutné pro správné zacházení s komplikacemi XML
(jmenné prostory), jiné nutné pro správné fungování rozhraní (zásobník
otevřených tagů) a další zcela zbytečné (neptejte, hrůzy, na které jsem
narazil, mě do dneška budí ze spaní).
Když ale používám zjednodušené API, které samo uzavírá tagy, můžu pod ním
podtrhnout ubrus a nahradit XMLStreamWriter za vlastní kód. Když vynechám
některé záludnosti XML jako jmenné prostory, není to nic komplikovaného.
Escapování XML je triviální – stačí si hlídat jen znaky & < > " a ten poslední
jen v některých případech. Dohromady pod 80 řádků kódu, který generuje XML za
letu, nealokuje skoro nic a je rychlejší než XMLStreamWriter
.
+1: Pro extrakci dat z XML je stále nejšikovnější starý dobrý XPath. Kdysi jsem napsal malou knihovnu xpath.scala, která XPath dodá trochu typovosti.