{"id":16965015,"url":"https://github.com/propensive/xylophone","last_synced_at":"2025-03-17T08:37:47.668Z","repository":{"id":39483018,"uuid":"81366093","full_name":"propensive/xylophone","owner":"propensive","description":"Working with XML in Scala","archived":false,"fork":false,"pushed_at":"2025-02-11T23:58:16.000Z","size":3390,"stargazers_count":23,"open_issues_count":7,"forks_count":1,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-10T19:53:49.233Z","etag":null,"topics":["scala","xml","xml-api","xml-parser","xml-serialization"],"latest_commit_sha":null,"homepage":"https://propensive.com/xylophone/","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/propensive.png","metadata":{"files":{"readme":".github/readme.md","changelog":null,"contributing":".github/contributing.md","funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-02-08T19:13:05.000Z","updated_at":"2025-02-11T23:58:19.000Z","dependencies_parsed_at":"2023-11-16T09:26:44.641Z","dependency_job_id":"8c1c6c33-9940-487a-9d8a-b3b5ef5a163c","html_url":"https://github.com/propensive/xylophone","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/propensive%2Fxylophone","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/propensive%2Fxylophone/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/propensive%2Fxylophone/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/propensive%2Fxylophone/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/propensive","download_url":"https://codeload.github.com/propensive/xylophone/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243852499,"owners_count":20358271,"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":["scala","xml","xml-api","xml-parser","xml-serialization"],"created_at":"2024-10-13T23:44:49.926Z","updated_at":"2025-03-17T08:37:47.652Z","avatar_url":"https://github.com/propensive.png","language":"Scala","readme":"[\u003cimg alt=\"GitHub Workflow\" src=\"https://img.shields.io/github/actions/workflow/status/propensive/xylophone/main.yml?style=for-the-badge\" height=\"24\"\u003e](https://github.com/propensive/xylophone/actions)\n[\u003cimg src=\"https://img.shields.io/discord/633198088311537684?color=8899f7\u0026label=DISCORD\u0026style=for-the-badge\" height=\"24\"\u003e](https://discord.com/invite/MBUrkTgMnA)\n\u003cimg src=\"/doc/images/github.png\" valign=\"middle\"\u003e\n\n# Xylophone\n\n__Typesafe XML for Scala__\n\n_Xylophone_ is an XML library for Scala that takes advantage of many features of the language to\nprovide intuitive syntax for manipulating XML, as well as better typesafety and static checks.\n\n## Features\n\n- parse and represent XML in Scala\n- statically check XML in `x\"\"` interpolators\n- substitute standard and custom types (provided by typeclasses) into XML\n- automatically derive typeclasses to convert case classes and product types to and from XML\n- safe dynamic interface for accessing nested fields\n\n\n## Availability\n\n\n\n\n\n\n\n## Getting Started\n\n### Parsing\n\nA `Text` value containing XML may be parsed with,\n```scala\nXml.parse(text)\n```\nwhich will return an instance of `Xml`, or throw an `XmlParseError` if the XML is not well-formed.\n\n### XML Literals\n\n`Xml` values may also be constructed using the `x\"\"` interpolator. These will be checked for well-formedness\nat compiletime: all syntax must be valid, special characters escaped, and all tags must be closed and nested\ncorrectly.\n\n```scala\nval book = x\"\u003cbook\u003e\u003cauthor\u003eH. G. Wells\u003c/author\u003e\u003ctitle\u003eWar of the Worlds\u003c/title\u003e\u003c/book\u003e\"\n```\n\n### XML AST Representation\n\nAn `Xml` value is a general type representing XML in three different forms, subtypes of `Xml`:\n- `XmlDoc` is a complete XML document, and includes the `\u003c?xml...` header\n- `XmlNode` is a single XML node\n- `XmlFragment` is a _fragment_ of XML, which may include zero, one or many XML nodes\n\nWhile all three subtypes represent XML, there is some overlap in what may be represented by each,\nbut important differences in their behavior (ex, and most Xylophone methods are careful to return precisely-typed\nvalues.\n\nFurthermore, the `Xml` subtypes serve as wrappers around several other AST node types, subtypes of the `Ast`\nenumeration:\n - `Element`\n - `Comment`\n - `ProcessingInstruction`\n - `Textual`\n - `CData`\n - `Root`\n\nOf these types, `Element` and `Root` include fields which may include sequences of other `Ast` nodes, thereby\nforming a tree structure.\n\n### Accessing elements\n\nUnlike some other data definition languages such as JSON, XML requires a distinction to be made between a\nsingle node (`XmlNode`) and a sequence of nodes (`XmlFragment`). Multiple nodes with the same tag name\nmay exist as children of another node, while it is common for some variants of XML to use unique tag names\nfor every child node.\n\nBoth approaches are supported in Xylophone with very simple syntax.\n\nFor example, given the XML,\n```xml\n\u003clibrary\u003e\n  \u003cbook\u003e\n    \u003cauthor\u003eH. G. Wells\u003c/author\u003e\n    \u003ctitle\u003eThe War of the Worlds\u003c/title\u003e\n  \u003c/book\u003e\n  \u003cbook\u003e\n    \u003cauthor\u003eVirginia Woolf\u003c/author\u003e\n    \u003ctitle\u003eMrs. Dalloway\u003c/title\u003e\n  \u003c/book\u003e\n\u003c/library\u003e\n```\nas an instance of `XmlNode`, `library`, we can access the two book nodes with `library.book`, as an `XmlFragment`.\nSubsequently calling, `library.book.title` would return a new `XmlFragment` consisting of the titles of _both_ books,\nspecifically,\n```xml\n\u003ctitle\u003eThe War of the Worlds\u003c/title\u003e\n\u003ctitle\u003eMrs. Dalloway\u003c/title\u003e\n```\n\nGiven an `XmlFragment`, the *nth* node in the sequence may be accessed by applying the node index, for example,\n```scala\nlibrary.book.title(1)\n```\nwould return,\n```xml\n\u003ctitle\u003eMrs. Dalloway\u003c/title\u003e\n```\nas an `XmlNode`.\n\nThe same node could alternatively be accessed with `library.book(1).title(0)`. Note how `0` is applied to the\n`title` now (instead of `1`) since we want the first (`0`) `title` element of the second (`1`) 'book' element.\nThe application of `(0)` returns a single `XmlNode` instance rather than an `XmlFragment` instance.\n\nThe `apply` method of `XmlFragment` has a default value of `0` for convenience in the very common case where the\nfirst node is required, e.g. `library.book().title()`\n\nAn `XmlFragment` of all the elements inside an `XmlNode` can always be obtained by calling `*` on the `XmlNode`\nvalue. For example, `library.book().*` would return an `XmlFragment` of,\n```xml\n\u003ctitle\u003eThe War of the Worlds\u003c/title\u003e\n\u003cauthor\u003eH. G. Wells\u003c/author\u003e\n```\n\n### Extracting typed values\n\nAn `Xml` value is _dynamic_ in the sense that it could represent a single string value or deeply-nested structured\ndata. Usually we want to convert `Xml` values to other Scala types in order to use them. This can be achieved by\ncalling `as[T]` on the value, for an appropriate choice of `T`.\n\nFor example, the `XmlNode`,\n```scala\nval name: XmlNode = x\"\u003cauthor\u003eVirginia Woolf\u003c/author\u003e\"\n```\ncould be converted to a `Text` with, `name.as[Text]`. Or,\n```scala\nval age: XmlNode = x\"\u003cage\u003e18\u003c/age\u003e\"\n```\ncan be read as a `Long` with, `age.as[Long]`.\n\nThis works for `Text`s and primitive types. But will also work for case classes composed of these types (or\nof other nested case classes). For example, given the definition,\n```scala\ncase class Book(title: Text, author: Text)\n```\na book from the _library_ example above could be read with:\n```scala\nlibrary.book().as[Book]\n```\n\nThe `as` method can also extract collection types (e.g. `Set`, `List` or `Vector`) from an `Xml` value, so\n_all_ the books in the library could be accessed with,\n```scala\nlibrary.book.as[Set[Book]]\n```\n\nIn general, extraction requires a contextual `XmlReader` typeclass instance for the type to be extracted.\nThese exist on the `XmlReader` companion object for the basic types, collection types, product types\n(e.g. case classes) and coproduct types (e.g. enumerations), but other instances may be provided.\n\n### Writing to XML\n\nLikewise, these same types may be converted to `Xml` by calling the `xml` extension method on them, for\nexample, given,\n```scala\ncase class Book(title: Text, author: Text)\nval book = Book(t\"Mrs. Dalloway\", t\"Virginia Woolf\")\n```\nwe could create an `XmlNode` value of,\n```xml\n\u003cBook\u003e\n  \u003ctitle\u003eMrs. Dalloway\u003c/title\u003e\n  \u003cauthor\u003eVirginia Woolf\u003c/author\u003e\n\u003c/Book\u003e\n```\njust by calling `book.xml`.\n\nNote that the element labels will be taken from the case class's type name and field names. However, for\nnested case classes, a type name will only appear in the XML output for the outermost tag name, since the\nfield name will be used in these cases.\n\nThe type name will also appear in the repeated child nodes of XML produced from a collection type, for\nexample, writing the `List[Int]`, `List(1, 2, 3)` would produce the XML,\n```xml\n\u003cList\u003e\n  \u003cInt\u003e1\u003c/Int\u003e\n  \u003cInt\u003e2\u003c/Int\u003e\n  \u003cInt\u003e3\u003c/Int\u003e\n\u003c/List\u003e\n```\n\nIn general, the type name will be used for a node if the context does not suggest a more specific name.\n\nThe node nade may be controlled, however, using annotations. The `@xmlLabel` attribute may be applied\nto a case class or a case class field to change its name when written or read.\n\nFor example, the definition,\n```\n@xmlLabel(t\"book\")\ncase class Book(title: Text, author: Text, @xmlLabel(t\"type\") kind: Text)\n```\nwould ensure that a `Book` instance is written using the lower-case tag name, `book`, and the `kind`\nfield would be serialized the name `type` (which cannot be used so easily in Scala, as it's a keyword).\n\n### Serialization\n\nXML usually needs to be serialized to a string. Xylophone provides a `show` method that will serialize\nan `Xml` value to a `Text` value using a contextual `XmlPrinter`, of which two are available by default:\none which omits all unnecessary whitespace, and one which \"pretty prints\" the XML with indentation for\nnesting.\n\n\n\n\n\n## Status\n\nXylophone is classified as __fledgling__. For reference, Soundness projects are\ncategorized into one of the following five stability levels:\n\n- _embryonic_: for experimental or demonstrative purposes only, without any guarantees of longevity\n- _fledgling_: of proven utility, seeking contributions, but liable to significant redesigns\n- _maturescent_: major design decisions broady settled, seeking probatory adoption and refinement\n- _dependable_: production-ready, subject to controlled ongoing maintenance and enhancement; tagged as version `1.0.0` or later\n- _adamantine_: proven, reliable and production-ready, with no further breaking changes ever anticipated\n\nProjects at any stability level, even _embryonic_ projects, can still be used,\nas long as caution is taken to avoid a mismatch between the project's stability\nlevel and the required stability and maintainability of your own project.\n\nXylophone is designed to be _small_. Its entire source code currently consists\nof 717 lines of code.\n\n## Building\n\nXylophone will ultimately be built by Fury, when it is published. In the\nmeantime, two possibilities are offered, however they are acknowledged to be\nfragile, inadequately tested, and unsuitable for anything more than\nexperimentation. They are provided only for the necessity of providing _some_\nanswer to the question, \"how can I try Xylophone?\".\n\n1. *Copy the sources into your own project*\n   \n   Read the `fury` file in the repository root to understand Xylophone's build\n   structure, dependencies and source location; the file format should be short\n   and quite intuitive. Copy the sources into a source directory in your own\n   project, then repeat (recursively) for each of the dependencies.\n\n   The sources are compiled against the latest nightly release of Scala 3.\n   There should be no problem to compile the project together with all of its\n   dependencies in a single compilation.\n\n2. *Build with [Wrath](https://github.com/propensive/wrath/)*\n\n   Wrath is a bootstrapping script for building Xylophone and other projects in\n   the absence of a fully-featured build tool. It is designed to read the `fury`\n   file in the project directory, and produce a collection of JAR files which can\n   be added to a classpath, by compiling the project and all of its dependencies,\n   including the Scala compiler itself.\n   \n   Download the latest version of\n   [`wrath`](https://github.com/propensive/wrath/releases/latest), make it\n   executable, and add it to your path, for example by copying it to\n   `/usr/local/bin/`.\n\n   Clone this repository inside an empty directory, so that the build can\n   safely make clones of repositories it depends on as _peers_ of `xylophone`.\n   Run `wrath -F` in the repository root. This will download and compile the\n   latest version of Scala, as well as all of Xylophone's dependencies.\n\n   If the build was successful, the compiled JAR files can be found in the\n   `.wrath/dist` directory.\n\n## Contributing\n\nContributors to Xylophone are welcome and encouraged. New contributors may like\nto look for issues marked\n[beginner](https://github.com/propensive/xylophone/labels/beginner).\n\nWe suggest that all contributors read the [Contributing\nGuide](/contributing.md) to make the process of contributing to Xylophone\neasier.\n\nPlease __do not__ contact project maintainers privately with questions unless\nthere is a good reason to keep them private. While it can be tempting to\nrepsond to such questions, private answers cannot be shared with a wider\naudience, and it can result in duplication of effort.\n\n## Author\n\nXylophone was designed and developed by Jon Pretty, and commercial support and\ntraining on all aspects of Scala 3 is available from [Propensive\nO\u0026Uuml;](https://propensive.com/).\n\n\n\n## Name\n\nA _xylophone_ is a musical instrument made from wood (\"xylo-\") or trees, and it provides a representation of XML trees. \"Xylophone\" and \"XML\" begin with the same infrequently-used letter.\n\nIn general, Soundness project names are always chosen with some rationale,\nhowever it is usually frivolous. Each name is chosen for more for its\n_uniqueness_ and _intrigue_ than its concision or catchiness, and there is no\nbias towards names with positive or \"nice\" meanings—since many of the libraries\nperform some quite unpleasant tasks.\n\nNames should be English words, though many are obscure or archaic, and it\nshould be noted how willingly English adopts foreign words. Names are generally\nof Greek or Latin origin, and have often arrived in English via a romance\nlanguage.\n\n## Logo\n\nThe logo shows two angle brackets (or chevrons), representing the most significant symbols in XML, placed next to each other to look like a capital X.\n\n## License\n\nXylophone is copyright \u0026copy; 2025 Jon Pretty \u0026 Propensive O\u0026Uuml;, and\nis made available under the [Apache 2.0 License](/license.md).\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpropensive%2Fxylophone","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpropensive%2Fxylophone","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpropensive%2Fxylophone/lists"}