Skip to content

Instantly share code, notes, and snippets.

@TheDIM47
Last active March 18, 2018 16:02
Show Gist options
  • Save TheDIM47/dde67c23cfd3b5933b2ba604f29c16ed to your computer and use it in GitHub Desktop.
Save TheDIM47/dde67c23cfd3b5933b2ba604f29c16ed to your computer and use it in GitHub Desktop.
Simple scala-xml parser for XmlTv format
import scala.xml._
import java.util.Date
import java.lang.ThreadLocal
import java.text.SimpleDateFormat
import scala.collection.immutable._
/**
* Simple XmlTv format parser
* http://wiki.xmltv.org/index.php/XMLTVFormat
*/
sealed case class Channel(
id: String,
displayName: Seq[String] = Seq.empty,
icon: Option[String] = None
)
sealed case class Programm(
start: Date,
stop: Date,
channel: Channel,
title: Map[String, String] = Map.empty,
description: Map[String, String] = Map.empty,
credits: Map[String, List[String]] = Map.empty,
category: Map[String, List[String]] = Map.empty,
episode: Map[String, List[String]] = Map.empty,
rating: Map[String, List[String]] = Map.empty,
stars: String, // star-rating
audio: Map[String, String] = Map.empty,
video: Map[String, String] = Map.empty,
prevShown: Option[Date] = None,
date: Option[Date] = None,
subtitlesType: String,
country: String
)
sealed case class Tv(
sourceInfoUrl: String,
sourceInfoName: String,
generatorInfoUrl: String,
generatorInfoName: String,
channels: Map[String, Channel],
programs: Map[Channel, Seq[Programm]]
)
object XmlTvParser {
val defaultChannel = new Channel("xmltv-default")
val DateFormatters = ListMap[Int, ThreadLocal[SimpleDateFormat]]() ++ List(
"yyyyMMddHHmmss Z", "yyyyMMddHHmmss", "yyyyMMddHHmm", "yyyyMMdd"
).map(s => s.length() -> new ThreadLocal[SimpleDateFormat]() {
override def initialValue(): SimpleDateFormat = new SimpleDateFormat(s)
}).toMap
implicit final class MyStringOps(val s: String) extends AnyVal {
def toOption: Option[String] = for (v <- Option(s) if s.trim.nonEmpty) yield v
}
implicit class MySeqOps(seq: Seq[(String, String)]) {
// def toFlattenMap: Map[String, List[String]] = seq.groupBy(_._1).map { case (k, v) => (k, v.map(_._2).toList) }
def toFlattenMap: Map[String, List[String]] = seq.groupBy(_._1).mapValues(_.map(_._2).toList)
}
def parseToDate(s: String): Date = {
val len = s.length
val r = XmlTvParser.DateFormatters.find{case (k,v) => k <= len }
r.map(_._2.get().parse(s)).getOrElse(throw new IllegalArgumentException(s))
}
def parseChannel(ch: Node): Channel = new Channel(
id = (ch \ "@id").text,
displayName = (ch \ "display-name").map(_.text),
icon = (ch \ "icon" \ "@src").text.toOption
)
def parseProgramm(p: Node, channels: Map[String, Channel]): Programm =
new Programm(
start = parseToDate((p \ "@start").text), // "20080715003000 -0600"
stop = parseToDate((p \ "@stop").text), // "20080715010000 -0600"
channel = channels.getOrElse((p \ "@channel").text, defaultChannel), // "I10436.labs.zap2it.com"
title = (p \ "title").map(v => (v \ "@lang").text -> v.text).toMap,
description = (p \ "desc").map(v => (v \ "@lang").text -> v.text).toMap,
credits = (p \ "credits").map(v => v.label -> v.text).toFlattenMap,
category = (p \ "category").map(v => (v \ "@lang").text -> v.text).toFlattenMap,
episode = (p \ "episode-num").map(v => (v \ "@system").text -> v.text).toFlattenMap,
rating = (p \ "rating").map(v => (v \ "@system").text -> (v \ "value").text).toFlattenMap,
stars = (p \ "star-rating" \ "value").text,
audio = (p \ "audio").map(v => v.label -> v.text).toMap,
video = (p \ "video").map(v => v.label -> v.text).toMap,
prevShown = (p \ "previously-shown" \ "@start").text.toOption.map(parseToDate),
date = (p \ "date").text.toOption.map(parseToDate),
subtitlesType = (p \ "subtitles" \ "@type").text,
country = (p \ "country").text
)
def parseTv(p: Node): Tv = {
val channelMap = (p \ "channel").map(parseChannel).map(v => v.id -> v).toMap
val programMap = (p \ "programme").map(v => parseProgramm(v, channelMap)).groupBy(_.channel)
Tv(
sourceInfoUrl = (p \ "@source-info-url").text,
sourceInfoName = (p \ "@source-info-name").text,
generatorInfoUrl = (p \ "@generator-info-url").text,
generatorInfoName = (p \ "@generator-info-name").text,
channelMap,
programMap
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment