0xDEADBEEF

[RSS]
««« »»»

Čím nahradit Scala.XML

23. 10. 2018

Ve Scale je možné v pro­gramu za­pi­so­vat přímo XML li­te­rály. Ještě pořád, ale už ne příliš dlouho. Martin Ode­r­sky (a jeho gang) není jejich fanda, už roky se jich chce zbavit a ve verzi 2.13 se mu to možná ko­nečně podaří.

Tech­nicky vzato XML li­te­rály nejsou třeba. Jazyk od verze 2.10 dis­po­nuje in­ter­po­lace stringů, která dokáže tuto funk­ci­o­na­litu beze zbytku na­hra­dit. 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 ve­sta­věné XML, parser se zjed­no­duší, XML v něm nebude mít vý­sadní po­sta­vení a nebude vázané na ne­o­hra­bané API scala.xml. Půjde jen o další z mnoha uži­va­tel­ských kniho­ven. Z těchto důvodů bychom s ním neměli dlou­ho­době po­čí­tat. Čím ho tedy můžeme na­hra­dit?

XML li­te­rály jsou často po­u­ží­vané na ge­ne­ro­vání RSS, Atomu a jiných XML vý­stupů. Vy­tvoří se DOM, pak je oka­mžitě pře­mě­něn na ře­tě­zec a za­ho­zen.

(<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ře­hledně, ale má to jeden ne­do­sta­tek: Po­staví se kom­pletní DOM se všemi alo­ka­cemi jen proto, aby byl oka­mžitě za­ho­zen. V pří­padě ge­ne­ro­vání vý­stupů bude stačit něco jed­no­duš­šího. Ne­po­tře­buji re­pre­zen­taci XML stromu, ne­po­tře­buji ho zkou­mat a měnit, stačí jen přímo vypsat jeho obsah. Pokud možno pře­hledně.

Tento účel splní starý dobrý XML­Stre­a­mWri­ter za­ba­lený do pře­hled­ného API. Vy­tvo­ření stej­ného RSS do­ku­mentu pak může vy­pa­dat 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 od­po­ví­da­jící writeEndElement, tak aby se ne­kří­žily, le­xi­kální blok se o po­stará o uza­ví­rání tagů a jejich vno­řo­vání. Vý­sle­dek je mě­ři­telně rych­lejší než ge­ne­ro­vání pomocí scala.xml, ale uká­zalo se, že čísla jsou ne­stálá a někdy divoce skáčou.

Pří­činu odhalí pohled do zdro­jáků. Z nich je vidět, že XMLStreamWriter je (jako mnoho ja­vov­ských API) určený pro zno­vupo­u­žití, ne jako jed­no­du­chý ná­stroj, který se po­u­žije jednou a pak je za­ho­zen. In­terní im­ple­men­tace alo­kuje mnoho ob­jektů, ně­které z nich nutné pro správné za­chá­zení s kom­pli­ka­cemi XML (jmenné pro­story), jiné nutné pro správné fun­go­vání roz­hraní (zá­sob­ník ote­vře­ných tagů) a další zcela zby­tečné (ne­ptejte, hrůzy, na které jsem na­ra­zil, mě do dneška budí ze spaní).

Když ale po­u­ží­vám zjed­no­du­šené API, které samo uza­vírá tagy, můžu pod ním pod­trh­nout ubrus a na­hra­dit XML­Stre­a­mWri­ter za vlastní kód. Když vy­ne­chám ně­které zá­lud­nosti XML jako jmenné pro­story, není to nic kom­pli­ko­va­ného. Es­ca­po­vání XML je tri­vi­ální – stačí si hlídat jen znaky & < > " a ten po­slední jen v ně­kte­rých pří­pa­dech. Do­hro­mady pod 80 řádků kódu, který ge­ne­ruje XML za letu, ne­a­lo­kuje skoro nic a je rych­lejší než XMLStreamWriter.


+1: Pro ex­trakci dat z XML je stále nej­ši­kov­nější starý dobrý XPath. Kdysi jsem napsal malou kni­hovnu xpath.scala, která XPath dodá trochu ty­po­vosti.

píše k47 (@kaja47, k47)