{"id":17343262,"url":"https://github.com/geirolz/cats-xml","last_synced_at":"2025-04-14T06:23:55.367Z","repository":{"id":39922559,"uuid":"470223461","full_name":"geirolz/cats-xml","owner":"geirolz","description":"A functional library to work with XML in Scala using Cats.","archived":false,"fork":false,"pushed_at":"2024-10-24T10:56:18.000Z","size":608,"stargazers_count":27,"open_issues_count":0,"forks_count":5,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-10-25T09:38:15.490Z","etag":null,"topics":["cats","decoding","encoding","fp","functional-programming","scala","xml"],"latest_commit_sha":null,"homepage":"https://geirolz.github.io/cats-xml/","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/geirolz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-03-15T15:34:59.000Z","updated_at":"2024-10-24T10:56:23.000Z","dependencies_parsed_at":"2023-10-22T01:24:58.839Z","dependency_job_id":"aa1aafba-56ab-4453-9191-b1d04b27259d","html_url":"https://github.com/geirolz/cats-xml","commit_stats":null,"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/geirolz%2Fcats-xml","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/geirolz%2Fcats-xml/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/geirolz%2Fcats-xml/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/geirolz%2Fcats-xml/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/geirolz","download_url":"https://codeload.github.com/geirolz/cats-xml/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248831382,"owners_count":21168458,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cats","decoding","encoding","fp","functional-programming","scala","xml"],"created_at":"2024-10-15T16:08:55.318Z","updated_at":"2025-04-14T06:23:55.344Z","avatar_url":"https://github.com/geirolz.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"# cats-xml\n\n[![Build Status](https://github.com/geirolz/cats-xml/actions/workflows/cicd.yml/badge.svg)](https://github.com/geirolz/cats-xml/actions)\n[![codecov](https://img.shields.io/codecov/c/github/geirolz/cats-xml)](https://codecov.io/gh/geirolz/cats-xml)\n[![Codacy Badge](https://app.codacy.com/project/badge/Grade/3101ec45f0114ad0abde91181c8c238c)](https://www.codacy.com/gh/geirolz/cats-xml/dashboard?utm_source=github.com\u0026amp;utm_medium=referral\u0026amp;utm_content=geirolz/cats-xml\u0026amp;utm_campaign=Badge_Grade)\n[![Sonatype Nexus (Releases)](https://img.shields.io/nexus/r/com.github.geirolz/cats-xml-core_2.13?server=https%3A%2F%2Foss.sonatype.org)](https://mvnrepository.com/artifact/com.github.geirolz/cats-xml-core)\n[![javadoc.io](https://javadoc.io/badge2/com.github.geirolz/cats-xml-core_2.13/javadoc.io.svg)](https://javadoc.io/doc/com.github.geirolz/cats-xml-core_2.13)\n[![Scala Steward badge](https://img.shields.io/badge/Scala_Steward-helping-blue.svg?style=flat\u0026logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAQCAMAAAARSr4IAAAAVFBMVEUAAACHjojlOy5NWlrKzcYRKjGFjIbp293YycuLa3pYY2LSqql4f3pCUFTgSjNodYRmcXUsPD/NTTbjRS+2jomhgnzNc223cGvZS0HaSD0XLjbaSjElhIr+AAAAAXRSTlMAQObYZgAAAHlJREFUCNdNyosOwyAIhWHAQS1Vt7a77/3fcxxdmv0xwmckutAR1nkm4ggbyEcg/wWmlGLDAA3oL50xi6fk5ffZ3E2E3QfZDCcCN2YtbEWZt+Drc6u6rlqv7Uk0LdKqqr5rk2UCRXOk0vmQKGfc94nOJyQjouF9H/wCc9gECEYfONoAAAAASUVORK5CYII=)](https://scala-steward.org)\n[![GitHub license](https://img.shields.io/github/license/geirolz/cats-xml)](https://github.com/geirolz/cats-xml/blob/master/LICENSE)\n\nA functional library to work with XML in Scala using cats core.\n\n```sbt\nlibraryDependencies += \"com.github.geirolz\" %% \"cats-xml\" % \"0.0.18\"\n```\n\nThis library is not production ready yet. There is a lot of work to do to complete it:\n- [X] Macros to derive `Encoder` and `Decoder` for Scala 2\n- [X] Reach a good code coverage with the tests (using munit) above 60%\n- [X] Support XPath\n- [X] `Decoder` and `Encoder` for primitives with error accumulating\n- [X] Good error handling and messaging \n- [X] Integration with standard scala xml library\n- [X] Integration with cats-effect to load files effectfully\n- [ ] Macros to derive `Encoder` and `Decoder` for Scala 3\n- [ ] Performance benchmarks\n- [ ] Integration with Tapir and Http4s\n- [ ] Literal macros to check XML strings at compile time\n\nContributions are more than welcome 💪\n\nPlease, drop a ⭐️ if you are interested in this project and you want to support it \n\n## Modules\n- [Effect](docs/compiled/effect.md)\n- [Generic (scala 2 only so far)](docs/compiled/generic.md)\n- [ScalaXml](docs/compiled/scalaxml.md)\n- [XPath](docs/compiled/xpath.md)\n\n## Example\nGiven\n```scala\ncase class Foo(\n    foo: Option[String], \n    bar: Int, \n    text: Boolean\n)\n```\n\n### Plain creation\n\n```scala\nimport cats.xml.XmlNode\nimport cats.xml.implicits.*\nimport cats.implicits.*\n\nval optNode: Option[XmlNode] = None\n// optNode: Option[XmlNode] = None\nval node: XmlNode =\n  XmlNode(\"Wrapper\")\n    .withAttrs(\n      \"a\" := 1,\n      \"b\" := \"test\",\n      \"c\" := Some(2),\n      \"d\" := None,\n    )\n    .withChildren(\n      XmlNode(\"Root\").withChildren(\n        XmlNode.group(\n          XmlNode(\"A\").withText(1),\n          XmlNode(\"B\").withText(\"2\"),\n          XmlNode(\"C\").withText(Some(3)),\n          XmlNode(\"D\").withText(None),\n          optNode.orXmlNull\n        )\n      )\n    )\n// node: XmlNode = \u003cWrapper a=\"1\" b=\"test\" c=\"2\" \u003e\n//  \u003cRoot\u003e\n//   \u003cA\u003e1\u003c/A\u003e\n//   \u003cB\u003e2\u003c/B\u003e\n//   \u003cC\u003e3\u003c/C\u003e\n//   \u003cD/\u003e\n//  \u003c/Root\u003e\n// \u003c/Wrapper\u003e\n```\n\n### Decoding\n```scala\nimport cats.xml.codec.Decoder\nimport cats.xml.implicits.*\nimport cats.implicits.*\n\nval decoder: Decoder[Foo] =\n  Decoder.fromCursor(c =\u003e\n    (\n      c.attr(\"name\").as[Option[String]],\n      c.attr(\"bar\").as[Int],\n      c.text.as[Boolean]\n    ).mapN(Foo.apply)\n  )\n```\n\n### Encoding\n\n```scala\nimport cats.xml.XmlNode\nimport cats.xml.codec.Encoder\n\nval encoder: Encoder[Foo] = Encoder.of(t =\u003e\n  XmlNode(\"Foo\")\n    .withAttrs(\n      \"foo\" := t.foo.getOrElse(\"ERROR\"),\n      \"bar\" := t.bar\n    )\n    .withText(t.text)\n)\n```\n\n### Navigating\n\n```scala\nimport cats.xml.XmlNode\nimport cats.xml.cursor.Cursor\nimport cats.xml.cursor.FreeCursor\nimport cats.xml.implicits.*\n\nval node =\n  xml\"\"\"\n     \u003cwrapper\u003e\n         \u003croot\u003e\n           \u003cfoo\u003e1\u003c/foo\u003e\n           \u003cbaz\u003e2\u003c/baz\u003e\n           \u003cbar\u003e3\u003c/bar\u003e\n         \u003c/root\u003e\n     \u003c/wrapper\u003e\"\"\"\n// node: XmlNode = \u003cwrapper\u003e\n//  \u003croot\u003e\n//   \u003cfoo\u003e1\u003c/foo\u003e\n//   \u003cbaz\u003e2\u003c/baz\u003e\n//   \u003cbar\u003e3\u003c/bar\u003e\n//  \u003c/root\u003e\n// \u003c/wrapper\u003e\n\nval fooNode: Cursor.Result[XmlNode] = node.focus(_.root.foo)\n// fooNode: Cursor.Result[XmlNode] = Right(value = \u003cfoo\u003e1\u003c/foo\u003e)\nval fooTextValue: FreeCursor.Result[Int] = node.focus(_.root.foo.text.as[Int])\n// fooTextValue: FreeCursor.Result[Int] = Valid(a = 1)\n```\n\n### Modifying\n```scala\nimport cats.xml.XmlNode\nimport cats.xml.modifier.Modifier\nimport cats.xml.implicits.*\n\nval node = xml\"\"\"\n     \u003cwrapper\u003e\n         \u003croot\u003e\n           \u003cfoo\u003e\n             \u003cbaz\u003e\n               \u003cbar\u003e\n                 \u003cvalue\u003e1\u003c/value\u003e\n               \u003c/bar\u003e\n             \u003c/baz\u003e\n           \u003c/foo\u003e\n         \u003c/root\u003e\n       \u003c/wrapper\u003e\"\"\"\n// node: XmlNode = \u003cwrapper\u003e\n//  \u003croot\u003e\n//   \u003cfoo\u003e\n//    \u003cbaz\u003e\n//     \u003cbar\u003e\n//      \u003cvalue\u003e1\u003c/value\u003e\n//     \u003c/bar\u003e\n//    \u003c/baz\u003e\n//   \u003c/foo\u003e\n//  \u003c/root\u003e\n// \u003c/wrapper\u003e\n\nval result: Modifier.Result[XmlNode] = node.modify(_.root.foo.baz.bar.value.modifyNode(_.withText(2)))\n// result: Modifier.Result[XmlNode] = Right(\n//   value = \u003cwrapper\u003e\n//  \u003croot\u003e\n//   \u003cfoo\u003e\n//    \u003cbaz\u003e\n//     \u003cbar\u003e\n//      \u003cvalue\u003e2\u003c/value\u003e\n//     \u003c/bar\u003e\n//    \u003c/baz\u003e\n//   \u003c/foo\u003e\n//  \u003c/root\u003e\n// \u003c/wrapper\u003e\n// )\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgeirolz%2Fcats-xml","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgeirolz%2Fcats-xml","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgeirolz%2Fcats-xml/lists"}