cats-xml
A functional library to work with XML in Scala using cats core.
libraryDependencies += "com.github.geirolz" %% "cats-xml" % "0.0.18"
This library is not production ready yet. There is a lot of work to do to complete it:
- Macros to derive
Encoder
andDecoder
for Scala 2 - Reach a good code coverage with the tests (using munit) above 60%
- Support XPath
Decoder
andEncoder
for primitives with error accumulating- Good error handling and messaging
- Integration with standard scala xml library
- Integration with cats-effect to load files effectfully
- Macros to derive
Encoder
andDecoder
for Scala 3 - Performance benchmarks
- Integration with Tapir and Http4s
- Literal macros to check XML strings at compile time
Contributions are more than welcome 💪
Please, drop a ⭐️ if you are interested in this project and you want to support it
Modules
Example
Given
case class Foo(
foo: Option[String],
bar: Int,
text: Boolean
)
Plain creation
import cats.xml.XmlNode
import cats.xml.implicits.*
import cats.implicits.*
val optNode: Option[XmlNode] = None
// optNode: Option[XmlNode] = None
val node: XmlNode =
XmlNode("Wrapper")
.withAttrs(
"a" := 1,
"b" := "test",
"c" := Some(2),
"d" := None,
)
.withChildren(
XmlNode("Root").withChildren(
XmlNode.group(
XmlNode("A").withText(1),
XmlNode("B").withText("2"),
XmlNode("C").withText(Some(3)),
XmlNode("D").withText(None),
optNode.orXmlNull
)
)
)
// node: XmlNode = <Wrapper a="1" b="test" c="2" >
// <Root>
// <A>1</A>
// <B>2</B>
// <C>3</C>
// <D/>
// </Root>
// </Wrapper>
Decoding
import cats.xml.codec.Decoder
import cats.xml.implicits.*
import cats.implicits.*
val decoder: Decoder[Foo] =
Decoder.fromCursor(c =>
(
c.attr("name").as[Option[String]],
c.attr("bar").as[Int],
c.text.as[Boolean]
).mapN(Foo.apply)
)
Encoding
import cats.xml.XmlNode
import cats.xml.codec.Encoder
val encoder: Encoder[Foo] = Encoder.of(t =>
XmlNode("Foo")
.withAttrs(
"foo" := t.foo.getOrElse("ERROR"),
"bar" := t.bar
)
.withText(t.text)
)
Navigating
import cats.xml.XmlNode
import cats.xml.cursor.Cursor
import cats.xml.cursor.FreeCursor
import cats.xml.implicits.*
val node =
xml"""
<wrapper>
<root>
<foo>1</foo>
<baz>2</baz>
<bar>3</bar>
</root>
</wrapper>"""
// node: XmlNode = <wrapper>
// <root>
// <foo>1</foo>
// <baz>2</baz>
// <bar>3</bar>
// </root>
// </wrapper>
val fooNode: Cursor.Result[XmlNode] = node.focus(_.root.foo)
// fooNode: Cursor.Result[XmlNode] = Right(value = <foo>1</foo>)
val fooTextValue: FreeCursor.Result[Int] = node.focus(_.root.foo.text.as[Int])
// fooTextValue: FreeCursor.Result[Int] = Valid(a = 1)
Modifying
import cats.xml.XmlNode
import cats.xml.modifier.Modifier
import cats.xml.implicits.*
val node = xml"""
<wrapper>
<root>
<foo>
<baz>
<bar>
<value>1</value>
</bar>
</baz>
</foo>
</root>
</wrapper>"""
// node: XmlNode = <wrapper>
// <root>
// <foo>
// <baz>
// <bar>
// <value>1</value>
// </bar>
// </baz>
// </foo>
// </root>
// </wrapper>
val result: Modifier.Result[XmlNode] = node.modify(_.root.foo.baz.bar.value.modifyNode(_.withText(2)))
// result: Modifier.Result[XmlNode] = Right(
// value = <wrapper>
// <root>
// <foo>
// <baz>
// <bar>
// <value>2</value>
// </bar>
// </baz>
// </foo>
// </root>
// </wrapper>
// )