Skip to content

geirolz/cats-xml

Folders and files

NameName
Last commit message
Last commit date
Dec 9, 2024
Oct 18, 2024
Oct 18, 2024
Jun 1, 2024
Oct 24, 2024
Dec 8, 2024
Feb 25, 2024
Aug 19, 2022
Aug 10, 2022
May 18, 2024
Feb 23, 2022
Jul 28, 2024
Feb 23, 2022
Feb 23, 2022
Feb 23, 2022
Oct 18, 2024
Feb 23, 2022
Nov 15, 2024
Feb 23, 2022

Repository files navigation

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.18"

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

  • Macros to derive Encoder and Decoder for Scala 2
  • Reach a good code coverage with the tests (using munit) above 60%
  • Support XPath
  • Decoder and Encoder 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 and Decoder 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>
// )