geirolz / cats-xml   0.0.3

Apache License 2.0 Website GitHub

A WIP functional library to work with XML in Scala using Cats.

Scala versions: 2.13 3.x

cats-xml

Build Status codecov Codacy Badge Sonatype Nexus (Releases) javadoc.io Scala Steward badge GitHub license

A functional library to work with XML in Scala using cats core.

libraryDependencies += "com.github.geirolz" %% "cats-xml" % "0.0.3"

This library is not production ready yet. There is a lot of work to do to complete it:

  • There are some performance issues loading xml from strings or files due the fact that there is a double conversion, We need to parse bytes directly into cats-xml classes. - Creates macro to derive Decoder and Encoder. This is not straightforward, distinguish between a Node and an Attribute ca can be done in some way thinking about attributes with primitives and value classes BUT distinguish between a Node/Attribute and Text is hard, probably an annotation or a custom Decoder/Encoder is required.
  • Reach a good code coverage with the tests (using munit)
  • Improve documentation
  • Literal macros to check XML strings at compile time

Contributions are more than welcome 💪

Modules

Example

Given

case class Foo(
    foo: Option[String], 
    bar: Int, 
    text: Boolean
)

Decoder

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)
  )

Encoder

import cats.xml.XmlNode
import cats.xml.codec.Encoder

val encoder: Encoder[Foo] = Encoder.of(t =>
  XmlNode("Foo")
    .withAttributes(
      "foo"  := t.foo.getOrElse("ERROR"),
      "bar"  := t.bar
    )
    .withText(t.text)
)

Modify XML

import cats.xml.cursor.NodeCursor.Root
import cats.xml.modifier.Modifier

val node: XmlNode = XmlNode("Foo")
  .withAttributes(
    "name" := "Foo",
    "age"  := 10
  )
  .withText("ORIGINAL")
// node: XmlNode = <Foo name="Foo" age="10">ORIGINAL</Foo>
  
val result: Modifier.Result[XmlNode] = Root
  .modifyIfNode(_.withText("NEW"))
  .apply(node)  
// result: Modifier.Result[XmlNode] = Right(
//   value = <Foo name="Foo" age="10">NEW</Foo>
// )