Quickstart

In this section there is a simple case presented in copy-paste ready form. In the next section it’s explained more deeply.

Add following lines to your build.sbt:

libraryDependencies ++= Seq(
	"pl.msitko" %% "xml-lens-io"     % "0.1.0",
	"pl.msitko" %% "xml-lens-optics" % "0.1.0"
)

In scala-js you basically need to replace %% with %%%. Because scala-js builds tend to be tricky due to various bundlers, there’s a sample project available.

Then in your Scala code you can:

import pl.msitko.xml.parsing.XmlParser
import pl.msitko.xml.printing.XmlPrinter
import pl.msitko.xml.dsl._

// some XML input
val input =
    """<?xml version="1.0" encoding="UTF-8"?>
      |<a>
      |  <e>item</e>
      |  <f>item</f>
      |  <g>item</g>
      |</a>""".stripMargin

// turn `f` node to upper case - define transformation
val modify = (root \ "f").hasTextOnly.modify(_.toUpperCase)

// parse XML
val parsed = XmlParser.parse(input).right.get

// apply transformation
val res = modify(parsed)

Now res contains modified XML document. Let’s print it out to verify if it’s what we intended:

println(XmlPrinter.print(res))
// <?xml version="1.0" encoding="UTF-8"?>
// <a>
//   <e>item</e>
//   <f>ITEM</f>
//   <g>item</g>
// </a>

Quickstart - explained

The whole example is about transforming one path of XML to be in upper case.

Firstly, we define the transformation in the following way:

import pl.msitko.xml.dsl._
// import pl.msitko.xml.dsl._

val modify = (root \ "f").hasTextOnly.modify(_.toUpperCase)
// modify: pl.msitko.xml.entities.XmlDocument => pl.msitko.xml.entities.XmlDocument = monocle.PTraversal$$Lambda$6146/1827520038@57116db5

root is available thanks to import pl.msitko.xml.dsl._. root stands for root element of XML document. Every XML document should contain exactly one root element. In case of our example <a> is a root element.

Then we can navigate deeper using \ operator. It takes a String argument which is a label of direct children we want to “zoom into”. There’s an overloaded \ operator which takes NameMatcher instead of String which allows you to specify namespace. You can read more about it in section Namespaces. \ taking String ignores namespaces - will match all nodes that have specified label.

Next, we narrow down our selection specifically to f nodes having only a text child. We do this by calling hasTextOnly.

After we specified what we want to modify, we can define what it should be modified to. We do this by using modify, which takes a function from String to String.

Now, when we have modify function declared we can use it on parsed XML. The code which parses input and run modify transformation on it looks like this:

import pl.msitko.xml.parsing.XmlParser
import pl.msitko.xml.printing.XmlPrinter

val parsed = XmlParser.parse(input).right.get

val res = modify(parsed)

Finally, we can print the result back to verify it:

XmlPrinter.print(res)
// res11: String =
// <?xml version="1.0" encoding="UTF-8"?>
// <a>
//   <e>item</e>
//   <f>ITEM</f>
//   <g>item</g>
// </a>

Where should I go from here

Recommended next reading is chapter on modularity and chapter covering Optics API. Those chapters will help you understanding how xml-lens API is structured.

In case of specific questions take a look at other chapters of this docs.