Cookbook
This chapter provides examples of common operations you may find useful when traversing and modifying XML trees.
All examples presented here should compile fine with following imports, if some additional import is needed it will be covered in example code:
import pl.msitko.xml.parsing.XmlParser
import pl.msitko.xml.printing.XmlPrinter
import pl.msitko.xml.dsl._
Table of contents
- How to perform a few transformations on the same XML document
- How to access n-th item of current focus
- How to deepen focus to all n-th children of current focus
- Transformation in scope of whole document
How to perform a few transformations on the same XML document
In most of examples in that documentation only one transformation to XML document was done. But since transformations
are plain function XmlDocument => XmlDocument
you can compose them with andThen
combinator. Example:
val input =
s"""<a>
| <c1>
| <f someKey="oldValue">item1</f>
| <f>item2</f>
| </c1>
|</a>""".stripMargin
val parsed = XmlParser.parse(input).right.get
val modifyAttr = (root \ "c1" \ "f").attr("someKey").set("newValue")
val modifyText = (root \ "c1" \ "f").hasTextOnly.modify(_.toUpperCase)
modifyAttr
is setting attribute value while modifyText
switch text of f
element to uppercase. We can compose
those two transformation with andThen
and apply composed transformation to XML document:
val modify = modifyAttr andThen modifyText
val modifiedDoc = modify(parsed)
The result:
XmlPrinter.print(modifiedDoc)
// res3: String =
// <a>
// <c1>
// <f someKey="newValue">ITEM1</f>
// <f>ITEM2</f>
// </c1>
// </a>
How to access n-th item of current focus
In the following XML you’re interested only in the second <f>
element (i.e. one containing item2
):
val input =
s"""<a>
| <c1>
| <f>item1</f>
| <f>item2</f>
| </c1>
|</a>""".stripMargin
val parsed = XmlParser.parse(input).right.get
You can access it using .index(n: Int)
combinator. Here’s the code:
// traversal is focused on both <f> elements
val traversal = (root \ "c1" \ "f")
// and now we limit our focus just to the second one
val optional = traversal.index(1)
val res = optional.hasTextOnly.modify(_.toUpperCase)(parsed)
XmlPrinter.print(res)
// res8: String =
// <a>
// <c1>
// <f>item1</f>
// <f>ITEM2</f>
// </c1>
// </a>
index(n: Int)
result type is Optional
which means that we limited our focus to just one element.
Crucial observation here is that we have not created a new focus (e.g. one going deeper) but just chosen
one item of current focus.
How to deepen focus to all n-th children of current focus
In the following XML you’re interested only in the second <f>
element (i.e. one containing item2
):
val input =
s"""<?xml version="1.0" encoding="UTF-8"?>
|<a>
| <f></f>
| <f>
| <h>abc</h>
| <i>toReplace</i>
| </f>
| <f>
| <h>abc</h>
| <i>toReplace</i>
| </f>
|</a>""".stripMargin
val parsed = XmlParser.parse(input).right.get
You can access it using .elementAt(n: Int)
. Here’s the code:
// traversal is focused on all three <f> elements
val traversal = (root \ "f")
// and now for each of those element we try to focus on its second child
val secondChildren = traversal.elementAt(1)
val res = secondChildren.hasTextOnly.modify(_.toUpperCase)(parsed)
XmlPrinter.print(res)
// res13: String =
// <?xml version="1.0" encoding="UTF-8"?>
// <a>
// <f></f>
// <f>
// <h>abc</h>
// <i>TOREPLACE</i>
// </f>
// <f>
// <h>abc</h>
// <i>TOREPLACE</i>
// </f>
// </a>
Transformation in scope of whole document
In previous examples we were always performing transformations on given path. In case when you need to do some modification
in scope of whole document monocle.function.Plated
may be useful. xml-lens
defined instances of Plated
for its types in
pl.msitko.xml.optics.OpticsInstances
.
For example, to change all Text
nodes to uppercase you can use following code:
import monocle.function.Plated
import pl.msitko.xml.optics.OpticsInstances._
import pl.msitko.xml.entities.LabeledElement
import pl.msitko.xml.optics.LabeledElementOptics
import pl.msitko.xml.optics.TextOptics
import pl.msitko.xml.optics.XmlDocumentOptics.rootLens
val input = """<a>
| <b>some text</b>
| <c>
| <d>another text</d>
| </c>
|</a>""".stripMargin
val parsed = XmlParser.parse(input).right.get
val transformation = Plated.transform[LabeledElement]{
LabeledElementOptics.allTexts.composeIso(TextOptics.textIso).modify(_.toUpperCase)
}_
val res = rootLens.modify(transformation)(parsed)
XmlPrinter.print(res)
// res17: String =
// <a>
// <b>SOME TEXT</b>
// <c>
// <d>ANOTHER TEXT</d>
// </c>
// </a>