dimanche 20 janvier 2013

Processing HTML with Scala as if XML

The controversial XML API for Scala is still usefull for simple use cases. However it comes short when dealing with HTML as found on public websites since it is not well formed.

import java.net.URL
import scala.xml.XML
val site = new URL("http://michel-daviot.blogspot.fr/")
XML.load(site)
//Exception in thread "main" org.xml.sax.SAXParseException; lineNumber: 7; columnNumber: 265;
//The entity name must immediately follow the '&' in the entity reference.
view raw XMLLoad.scala hosted with ❤ by GitHub



It is quite easy however to overcome this limitation by using the library tagsoup which allows to "fix" the HTML markup to make it look like XML.

You will add this dependency to your pom.xml :

<dependency>
<groupId>org.ccil.cowan.tagsoup</groupId>
<artifactId>tagsoup</artifactId>
<version>1.2.1</version>
</dependency>
view raw pom.xml hosted with ❤ by GitHub


This simple object can then be used to load an URL containing HTML markup as if it was XML.

import java.net.URL
import scala.xml.XML
import org.xml.sax.InputSource
import scala.xml.parsing.NoBindingFactoryAdapter
import org.ccil.cowan.tagsoup.jaxp.SAXFactoryImpl
import java.net.HttpURLConnection
import scala.xml.Node
object HTML {
lazy val adapter = new NoBindingFactoryAdapter
lazy val parser = (new SAXFactoryImpl).newSAXParser
def load(url: URL, headers: Map[String, String] = Map.empty): Node = {
val conn = url.openConnection().asInstanceOf[HttpURLConnection]
for ((k, v) <- headers)
conn.setRequestProperty(k, v)
val source = new InputSource(conn.getInputStream)
adapter.loadXML(source, parser)
}
}
view raw HTML.scala hosted with ❤ by GitHub


Note that it also allows to set HTTP headers to the request, for instance if you want to use a cookie or a sessionId to get logged in.

Example calling code, which will list all HTML links from the loaded page :

import java.net.URL
val site = new URL("http://michel-daviot.blogspot.fr/")
val content = HTML.load(site)
for (
a <- content \\ "a";
href = a.attribute("href");
if href.isDefined
) println(href.get)
view raw HtmlDemo.scala hosted with ❤ by GitHub


And as a bonus, an object to force proxy settings from the code (for sure, you could also set it from the command line).

object SetProxy {
def apply(proxyConfig: (String, Int)) {
val (host, port) = proxyConfig
for (protocol <- Seq("http", "https")) {
System.setProperty(s"$protocol.proxyPort", port.toString)
System.setProperty(s"$protocol.proxyHost", host)
}
}
}
view raw SetProxy.scala hosted with ❤ by GitHub

3 commentaires:

  1. How it behaves in case of html is not formatted with pure html tags?

    RépondreSupprimer
    Réponses
    1. This is exactly the point of the tagsoup library ! It will convert the html, even ill-formatted, to SAX events which will always give you some well-formatted XML.

      Supprimer
  2. Or you could use the validator.nu parser directly.

    RépondreSupprimer