k47.cz
foto Praha výběr povídky kulturatwitter FB


11. 11. 2012

XPath.scala 📷

       

Parsování XML a (špatného) HTML je nejspíš moje životní poslání. V nekonečné snaze najít ideální nástroj pro zpracování těchto formátů jsem napsal další mikro-knihovnu. Tentokrát jde o XPath.scala - jednoduchý Scala wrapper nad Jaxa XPath API, který se v mnohém se podobá mojí dřívější PHP kreaci Atrox\Matcher.


Javovské rozhraní pro práci s XPath je jednak netypované (vrací objekt typu Any, který si musíme sami přetypovat) a druhak dodržuje Javoskou tradici a je až legendárně ukecané. Oba tyto nedostatky jsem v XPath.scala vyřešil pomocí type-class, funkcionální abstrakce a haldou implicitních konverzí.

Základem je funkce xpath, které musíme předat požadovaný typ výsledku a XPath cestu. Výsledkem je jiná funkce, která na DOM dokumentu provede XPath dotaz a vrátí výsledek správného typu. XPath dotaz se zkompiluje a připraví ve funkci xpath, následující funkce ho už jenom vykonává.

def xpath[T](path: String): Any => T

Pužití je velice jednoduché:

// připravíme dotaz
val getTitle = xpath[String]("//div[@class='post']//h1")

// dotaz použijeme (nejlépe opakovaně)
val title: String = getTitle(domDocument)

A protože je výsledkem obyčejná funkce, můžeme jí skládat s dalšími funkcemi:

// funkce, která naparsuje titulek
val parseTitle: String => Title = ???

// nejdřív extrahujeme titulek, pak ho zkonvertujeme do objektu Title
val getAndParseTitle: AnyRef => Title =
  xpath[String]("//div[@class='post']//h1") andThen parseTitle

V případě kolekcí se nemusíme spoléhat jenom na typ NodeList z Javovského API, ale můžeme požadovat plně typované sekvence:

// chceme extrahovat sekvenci integerů
xpath[Seq[Int]]("//post/id")

// když se id nevejde do integeru, můžeme použít jiný typ
xpath[Seq[Long]]("//post/id")

// popřípadě vlastní typ
implicit val MyTypeConverter = ConvertNode[MyType] { n: Node => MyType(n.getTextContent) }
xpath[Seq[MyType]]("//post/id")

// což je obdoba tohoto
xpath[Seq[Node]]("//post/id") map (n => MyType(n.getTextContent))

// nebo tohohle
xpath[Seq[String]]("//post/id") map (MyType(_))

Volání jako například xpath[Seq[Int]] funguje to jenom proto, že funkce xpath si přes mechanismus implicitních argumentů najde type-class pro Seq a ta pak si zase najde příslušnou type-class pro Int.

S trochou představivosti se dá xpath.scala použít podobně jako Atrox\Matcher:

val matcher = xpath[Seq[Node]]("//post") andThen { _ map { post => new {
  val title = xpath[String]("title", post)
  val text  = xpath[String]("text", post)
  val tags  = xpath[Seq[String]]("tag", post) map (_.trim)
}}}

vstoupit do diskuze    sdílet na facebooku, twitteru, google+

příbuzné články:
lift-json 📷
Scala - líné parametry, líné závislosti a líné proxy 📷
Sleeping patterns
Conway's game of life
Atrox\Arr
Kleisli arrow 📷

sem odkazují:
Srovnání Scala.xml, Anti-xml a XPath 📷

píše k47 & NEVERYOUNG, kontakt: ascii@k47.cz