scalaintermediate

XML Processing and Parsing

Parse and generate XML in Scala: literals, pattern matching, XPath-like queries, and transformation.

scala
import scala.xml.*

@main def run(): Unit =
  // XML literals
  val book = <book isbn="978-0-13-468599-1">
    <title>Programming in Scala</title>
    <author>Martin Odersky</author>
    <year>2021</year>
    <price>49.99</price>
  </book>

  // Access elements
  println(s"Title: ${(book \ "title").text}")
  println(s"Author: ${(book \ "author").text}")
  println(s"ISBN: ${(book \@ "isbn")}")

  // XML with expressions
  def bookXml(title: String, author: String, price: Double) =
    <book>
      <title>{title}</title>
      <author>{author}</author>
      <price>{f"$price%.2f"}</price>
    </book>

  val myBook = bookXml("Scala 3", "Author", 39.99)
  println(myBook)

  // Collection of elements
  val library = <library>
    <book genre="programming">
      <title>Scala in Depth</title>
      <price>44.99</price>
    </book>
    <book genre="programming">
      <title>FP in Scala</title>
      <price>49.99</price>
    </book>
    <book genre="fiction">
      <title>Dune</title>
      <price>12.99</price>
    </book>
  </library>

  // Query all titles
  val titles = (library \ "book" \ "title").map(_.text)
  println(s"Titles: $titles")

  // Deep search with \\
  val allTitles = (library \\ "title").map(_.text)
  println(s"Deep titles: $allTitles")

  // Filter by attribute
  val progBooks = (library \ "book")
    .filter(b => (b \@ "genre") == "programming")
    .map(b => (b \ "title").text)
  println(s"Programming: $progBooks")

  // Transform XML
  val discounted = new RuleTransformer(new RewriteRule {
    override def transform(n: Node): Seq[Node] = n match
      case elem: Elem if elem.label == "price" =>
        val price = elem.text.toDouble
        <price>{f"${price * 0.9}%.2f"}</price>
      case other => other
  })

  val saleLibrary = discounted(library)
  println(s"Discounted:\n$saleLibrary")

  // Parse XML string
  val xmlStr = """<config><host>localhost</host><port>8080</port></config>"""
  val config = XML.loadString(xmlStr)
  println(s"Host: ${(config \ "host").text}")
  println(s"Port: ${(config \ "port").text}")

  // Pattern matching on XML
  myBook match
    case <book><title>{t}</title>{_*}</book> =>
      println(s"Matched title: $t")
    case _ => println("No match")

  // Generate XML from data
  case class Product(name: String, price: Double, qty: Int)
  val products = List(
    Product("Widget", 9.99, 100),
    Product("Gadget", 19.99, 50)
  )

  val catalog = <catalog>{
    products.map { p =>
      <product>
        <name>{p.name}</name>
        <price>{p.price}</price>
        <quantity>{p.qty}</quantity>
      </product>
    }
  }</catalog>
  println(s"Catalog:\n${new PrettyPrinter(80, 2).format(catalog)}")

Use Cases

  • XML configuration parsing
  • Data interchange format
  • XML document generation

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.