{"id":19854025,"url":"https://github.com/wall-brew-co/clj-xml","last_synced_at":"2025-05-02T01:30:27.233Z","repository":{"id":39622321,"uuid":"320837166","full_name":"Wall-Brew-Co/clj-xml","owner":"Wall-Brew-Co","description":"The missing link between clj and xml","archived":false,"fork":false,"pushed_at":"2025-04-22T01:23:50.000Z","size":2988,"stargazers_count":9,"open_issues_count":2,"forks_count":3,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-04-22T02:38:11.067Z","etag":null,"topics":["clj-xml","clojure","clojure-library","clojurescript","edn","xml","xml-document"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Wall-Brew-Co.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2020-12-12T13:28:23.000Z","updated_at":"2025-04-22T01:23:53.000Z","dependencies_parsed_at":"2024-01-07T19:42:20.827Z","dependency_job_id":"7151b657-5a1a-4055-96a2-3bba445d22a6","html_url":"https://github.com/Wall-Brew-Co/clj-xml","commit_stats":{"total_commits":74,"total_committers":5,"mean_commits":14.8,"dds":"0.32432432432432434","last_synced_commit":"a5e7183e1f3df845ea34af0a646e676b276a70be"},"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Wall-Brew-Co%2Fclj-xml","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Wall-Brew-Co%2Fclj-xml/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Wall-Brew-Co%2Fclj-xml/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Wall-Brew-Co%2Fclj-xml/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Wall-Brew-Co","download_url":"https://codeload.github.com/Wall-Brew-Co/clj-xml/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251969236,"owners_count":21673180,"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":["clj-xml","clojure","clojure-library","clojurescript","edn","xml","xml-document"],"created_at":"2024-11-12T14:08:22.184Z","updated_at":"2025-05-02T01:30:27.225Z","avatar_url":"https://github.com/Wall-Brew-Co.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# clj-xml\n\n[![Clojars Project](https://img.shields.io/clojars/v/com.wallbrew/clj-xml.svg)](https://clojars.org/com.wallbrew/clj-xml)\n[![cljdoc badge](https://cljdoc.org/badge/com.wallbrew/clj-xml)](https://cljdoc.org/d/com.wallbrew/clj-xml/CURRENT)\n[![GitHub](https://img.shields.io/github/license/Wall-Brew-Co/clj-xml)](https://github.com/Wall-Brew-Co/clj-xml/blob/master/LICENSE)\n[![Twitter Follow](https://img.shields.io/twitter/follow/WallBrew?style=social)](https://twitter.com/WallBrew)\n\nA clojure library designed to make conversions between EDN and XML a little easier.\n\nThis repository follows the guidelines and standards of the [Wall Brew Open Source Policy.](https://github.com/Wall-Brew-Co/open-source \"Our open source guidelines\")\n\n## Installation\n\nA deployed copy of the most recent version of [clj-xml can be found on clojars.](https://clojars.org/com.wallbrew/clj-xml)\nTo use it, add the following as a dependency in your project.clj file:\n\n[![Clojars Project](https://clojars.org/com.wallbrew/clj-xml/latest-version.svg)](https://clojars.org/com.wallbrew/clj-xml)\n\nThe next time you build your application, [Leiningen](https://leiningen.org/) or [deps.edn](https://clojure.org/guides/deps_and_cli) should pull it automatically.\nAlternatively, you may clone or fork the repository to work with it directly.\n\n### Clojure Version Compatibility\n\nclj-xml runs its test suite in a matrix, replacing the clojure version with multiple previous versions of the language.\nIf your project is not running the most recent version of clojure, please consult the most recent compatibility testing results in the [CI/CD pipeline](https://github.com/Wall-Brew-Co/clj-xml/actions/workflows/compatibility.yml).\n\n## Public Functions\n\n### XML Parsing\n\nThis library is built on top of `clojure.data.xml` and extracts the element structure they've implemented.\nBased on where your XML is and how it's stored, you'll want to use one of the three following functions to extract it.\n\n* When you have already parsed an XML document into `clojure.data.xml` elements: `xml-\u003eedn`\n* When you have a string containing an XML document: `xml-str-\u003eedn`\n* When you have a `java.io.InputStream` or `java.io.Reader` (generally used for HTTP/File System): `xml-source-\u003eedn`\n\nEach of these functions accepts an option map as an optional second argument, supporting the following keys:\n\n| Option                 | Default Value |Description                                                                                                                          |\n|------------------------|---------------|-------------------------------------------------------------------------------------------------------------------------------------|\n| `:limit-eagerness?`    | `false`       | A boolean, that if set to true, disables automatic coercion to vectors for returned sequences                                       |\n| `:preserve-keys?`      | `false`       |Maintain the exact keyword structure provided by `clojure.xml/parse`                                                                 |\n| `:preserve-attrs?`     | `false`       |Maintain embedded XML attributes                                                                                                     |\n| `:remove-empty-attrs?` | `false`       |Remove any empty attribute maps                                                                                                      |\n| `:stringify-values?`   | `false`       |Coerce non-nil, non-string, non-collection values to strings                                                                         |\n| `:remove-newlines?`    | `false`       |Remove any newline characters in `xml-str`. Only applicable for `xml-str-\u003eedn`                                                       |\n| `:force-seq?`          | `false`       |Coerce all child XML nodes into an array of maps.                                                                                    |\n| `:force-seq-for-paths` | `[]`          |A sequence of key-path sequences that will be selectively coerced into sequences. Read more about [Key Pathing](#key-pathing) below. |\n\n`xml-str-\u003eedn` and `xml-source-\u003eedn` also support the parsing options from `clojure.data.xml` and Java's `XMLInputFactory` class.\n[Additional documentation](http://docs.oracle.com/javase/6/docs/api/javax/xml/stream/XMLInputFactory.html) from Oracle is available.\nThis library does not override the default behavior of `XMLInputFactory`.\n\n| Option                          | Default Value               | Description                                                                             |\n|---------------------------------|-----------------------------|-----------------------------------------------------------------------------------------|\n| `:include-node?`                | `#{:element :characters}`   | A subset of #{:element :characters :comment}, representing the XML nodes to keep        |\n| `:location-info`                | `true`                      | Wether or not location metadata should be generated and attached to results             |\n| `:allocator`                    | Object created Just-In-Time | An instance of an XMLInputFactory/ALLOCATOR to allocate events                          |\n| `:coalescing`                   | `true`                      | Convert CDATA nodes to text, and append any adjacent text nodes                         |\n| `:namespace-aware`              | `false`                     | Wether or not XML 1.0 namespacing support is enabled                                    |\n| `:replacing-entity-references`  | `false`                     | Wether or not internal entity text should be replaced with its XML entity form          |\n| `:supporting-external-entities` | `false`                     | Wether or not externally hosted entities will be resolved at parsing time and evaluated |\n| `:validating`                   | `false`                     | Wether or not attached DTDs will be resolved and used to validate the XML document      |\n| `:reporter`                     | Object created Just-In-Time | An instance of a XMLInputFactory/REPORTER to use in place of default                    |\n| `:resolver`                     | Object created Just-In-Time | An instance of a XMLInputFactory/RESOLVER to use in place of defaults                   |\n| `:support-dtd`                  | `false`                     | Wether or not attached DTDs will be read by the parser                                  |\n| `:skip-whitespace`              | `false`                     | Wether or not any whitespace only elements will be preserved as nodes                   |\n\nLet's see how it works:\n\n```clojure\n(require [clj-xml.core :as xml])\n\n(def xml-example\n  {:tag :TEST_DOCUMENT\n   :attrs {:XMLNS \"https://www.fake.not/real\"}\n   :content\n   [{:tag :HEAD\n     :attrs nil\n     :content\n     [{:tag :META_DATA :attrs {:TYPE \"title\"} :content [\"Some Fake Data!\"]}\n      {:tag :META_DATA :attrs {:TYPE \"tag\"} :content [\"Example Content\"]}]}\n    {:tag :FILE\n     :attrs\n     {:POSTER \"JANE DOE \u003cj.doe@fake-email.not-real\u003e\"\n      :DATE \"2020/04/12\"\n      :SUBJECT \"TEST DATA\"}\n     :content\n     [{:tag :GROUPS\n       :attrs nil\n       :content\n       [{:tag :GROUP :attrs nil :content [\"test-data-club\"]}]}\n      {:tag :SEGMENTS\n       :attrs nil\n       :content\n       [{:tag :SEGMENT\n         :attrs {:BITS \"00111010\" :NUMBER \"58\"}\n         :content [\"more data\"]}\n        {:tag :SEGMENT\n         :attrs {:BYTES \"10100010\" :NUMBER \"-94\"}\n         :content [\"more fake data\"]}]}]}]})\n\n(xml/xml-\u003eedn xml-example)\n;; =\u003e {:test-document\n;;     {:head [{:meta-data \"Some Fake Data!\"}\n;;             {:meta-data \"Example Content\"}]\n;;      :file {:groups [{:group \"test-data-club\"}]\n;;             :segments [{:segment \"more data\"}\n;;                        {:segment \"more fake data\"}]}}}\n\n;; Parse a string instead\n(def xml-test-string\n  \"\u003c?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?\u003e\u003cTEST_DOCUMENT XMLNS=\\\"https://www.fake.not/real\\\"\u003e\u003cHEAD\u003e\u003cMETA_DATA TYPE=\\\"title\\\"\u003eSome Fake Data!\u003c/META_DATA\u003e\u003cMETA_DATA TYPE=\\\"tag\\\"\u003eExample Content\u003c/META_DATA\u003e\u003c/HEAD\u003e\u003cFILE POSTER=\\\"JANE DOE \u0026lt;j.doe@fake-email.not-real\u0026gt;\\\" DATE=\\\"2020/04/12\\\" SUBJECT=\\\"TEST DATA\\\"\u003e\u003cGROUPS\u003e\u003cGROUP\u003etest-data-club\u003c/GROUP\u003e\u003c/GROUPS\u003e\u003cSEGMENTS\u003e\u003cSEGMENT BITS=\\\"00111010\\\" NUMBER=\\\"58\\\"\u003emore data\u003c/SEGMENT\u003e\u003cSEGMENT BYTES=\\\"10100010\\\" NUMBER=\\\"-94\\\"\u003emore fake data\u003c/SEGMENT\u003e\u003c/SEGMENTS\u003e\u003c/FILE\u003e\u003c/TEST_DOCUMENT\u003e\")\n\n(xml/xml-str-\u003eedn xml-test-string)\n;; =\u003e {:test-document\n;;     {:head [{:meta-data \"Some Fake Data!\"}\n;;             {:meta-data \"Example Content\"}]\n;;      :file {:groups [{:group \"test-data-club\"}]\n;;             :segments [{:segment \"more data\"}\n;;                        {:segment \"more fake data\"}]}}}\n\n;; Preserve the XML_CASE\n(xml/xml-\u003eedn xml-example {:preserve-keys? true})\n;; =\u003e {:TEST_DOCUMENT\n;;     {:HEAD [{:META-DATA \"Some Fake Data!\"}\n;;             {:META-DATA \"Example Content\"}]\n;;      :FILE {:GROUPS [{:GROUP \"test-data-club\"}]\n;;             :SEGMENTS [{:SEGMENT \"more data\"}\n;;                        {:SEGMENT \"more fake data\"}]}}}\n\n;; Preserve the XML attributes\n(xml/xml-\u003eedn xml-example {:preserve-attrs? true})\n;; =\u003e   {:test-document\n;;      {:head [{:meta-data \"Some Fake Data!\" :meta-data-attrs {:type \"title\"}}\n;;              {:meta-data \"Example Content\" :meta-data-attrs {:type \"tag\"}}]\n;;       :file {:groups [{:group \"test-data-club\"}]\n;;              :segments [{:segment \"more data\" :segment-attrs {:bits \"00111010\" :number \"58\"}}\n;;                         {:segment \"more fake data\" :segment-attrs {:bytes \"10100010\" :number \"-94\"}}]}\n;;       :file-attrs {:poster \"JANE DOE \u003cj.doe@fake-email.not-real\u003e\"\n;;                    :date \"2020/04/12\"\n;;                    :subject \"TEST DATA\"}}\n;;      :test-document-attrs {:xmlns \"https://www.fake.not/real\"}}\n```\n\n#### Key Pathing\n\nWhen you want to ensure selective paths in the returned XML are coerced to sequences, you may pick one of two options:\n\n* `force-seq?`          - to coerce all child XML nodes into a collection of nodes.\n* `force-seq-for-paths` - to coerce selective child XML nodes into collections of nodes.\n\nIn the case of `force-seq-for-paths`, you will supply a sequence of key paths, each of which direct to children a la `assoc-in`.\nThis key path may contain three types of paths, which may be used together.\n\n* The namespace qualified keywords: `:clj-xml.core/first`, `:clj-xml.core/last`, and `:clj-xml.core/every`\n  * These will modify a sequence's first, last, or every child, respectively\n* The provided alias symbols for the above keywords: `first-child`, `last-child`, and `every-child`\n  * These will modify a sequence's first, last, or every child, respectively\n* Bare keywords\n  * These will modify the matching given keyword in a map a la `update`\n\nIf the key path is incongruent with the current data structure, (e.g. `every-child` and a hash-map), an exception will be thrown.\n\n```clojure\n(require [clj-xml.core :as xml])\n\n(xml/xml-\u003eedn' xml-example {:force-seq-for-paths [[:test-document :file :segments xml/every-child]\n                                                  [:test-document]]})\n;; =\u003e {:test-document\n;;     [{:head [{:meta-data \"Some Fake Data!\"}\n;;              {:meta-data \"Example Content\"}]\n;;      :file {:groups [{:group \"test-data-club\"}]\n;;             :segments [[{:segment \"more data\"}]\n;;                        [{:segment \"more fake data\"}\n\n(xml/xml-\u003eedn' xml-example {:force-seq-for-paths [[xml/every-child]]})\n;; =\u003e java.lang.IllegalArgumentException: The key clj-xml.core/every is incompatible with class clojure.lang.PersistentArrayMap\n```\n\n### XML Emission\n\nTo convert EDN into XML, you'll want to use one of the following functions based on the target location.\n\n* When you want `clojure.data.xml` elements: `edn-\u003exml`\n* When you want an unindented string containing an XML document: `edn-\u003exml-str`\n* When you have a `java.io.OutputputStream` or `java.io.Writer` (generally used for HTTP/File System): `edn-\u003exml-stream`\n\nEach of these functions accepts an option map as an optional final argument, supporting the following keys:\n\n| Option               | Default Value | Description                                                                                     |\n|----------------------|---------------|-------------------------------------------------------------------------------------------------|\n| `:limit-eagerness?`    | `false`       | A boolean, that if set to true, disables automatic coercion to vectors for returned sequences |\n| `:to-xml-case?`      | `false`       | Wether or not the keys representing XML tags will be coerced to XML_CASE                        |\n| `:from-xml-case?`    | `false`       | Wether or not the source EDN is in XML_CASE                                                     |\n| `:stringify-values?` | `false`       | Wether or not non-nil, non-string, non-collection values should be coerced to strings           |\n\n`edn-\u003exml-str` and `edn-\u003exml-stream` also support the parsing options from `clojure.data.xml`:\n\n| Option      | Default Value | Description                                                          |\n|-------------|---------------|----------------------------------------------------------------------|\n| `:encoding` | `UTF-8`       | A string representing the character encoding the encoder should emit |\n| `:doctype`  | `nil`         | An XML Doctype that should be written as the emitted document's DTD  |\n\nLet's see how it works:\n\n```clojure\n(require [clj-xml.core :as xml])\n\n(def edn-example-with-attrs-and-original-keys\n  {:TEST_DOCUMENT\n   {:HEAD [{:META_DATA \"Some Fake Data!\" :META_DATA_ATTRS {:TYPE \"title\"}}\n           {:META_DATA \"Example Content\" :META_DATA_ATTRS {:TYPE \"tag\"}}]\n    :FILE {:GROUPS [{:GROUP \"test-data-club\"}]\n           :SEGMENTS [{:SEGMENT \"more data\" :SEGMENT_ATTRS {:BITS \"00111010\" :NUMBER \"58\"}}\n                      {:SEGMENT \"more fake data\" :SEGMENT_ATTRS {:BYTES \"10100010\" :NUMBER \"-94\"}}]}\n    :FILE_ATTRS {:POSTER \"JANE DOE \u003cj.doe@fake-email.not-real\u003e\"\n                 :DATE \"2020/04/12\"\n                 :SUBJECT \"TEST DATA\"}}\n   :TEST_DOCUMENT_ATTRS {:XMLNS \"https://www.fake.not/real\"}})\n\n(xml/edn-\u003exml-str edn-example-with-attrs-and-original-keys {:to-xml-case? true :from-xml-case? true :stringify-values? true})\n;; =\u003e \"\u003c?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?\u003e\u003cTEST_DOCUMENT XMLNS=\\\"https://www.fake.not/real\\\"\u003e\u003cHEAD\u003e\u003cMETA_DATA TYPE=\\\"title\\\"\u003eSome Fake Data!\u003c/META_DATA\u003e\u003cMETA_DATA TYPE=\\\"tag\\\"\u003eExample Content\u003c/META_DATA\u003e\u003c/HEAD\u003e\u003cFILE POSTER=\\\"JANE DOE \u0026lt;j.doe@fake-email.not-real\u0026gt;\\\" DATE=\\\"2020/04/12\\\" SUBJECT=\\\"TEST DATA\\\"\u003e\u003cGROUPS\u003e\u003cGROUP\u003etest-data-club\u003c/GROUP\u003e\u003c/GROUPS\u003e\u003cSEGMENTS\u003e\u003cSEGMENT BITS=\\\"00111010\\\" NUMBER=\\\"58\\\"\u003emore data\u003c/SEGMENT\u003e\u003cSEGMENT BYTES=\\\"10100010\\\" NUMBER=\\\"-94\\\"\u003emore fake data\u003c/SEGMENT\u003e\u003c/SEGMENTS\u003e\u003c/FILE\u003e\u003c/TEST_DOCUMENT\u003e\"\n\n```\n\n## Contributors\n\n\u003ca href=\"https://github.com/Wall-Brew-Co/clj-xml/graphs/contributors\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/Wall-Brew-Co/clj-xml/master/CONTRIBUTORS.svg\" alt=\"The GitHub profile pictures of all current contributors. Clicking this image will lead you to the GitHub contribution graph.\" /\u003e\u003c/a\u003e\n\n## License\n\nCopyright © [Wall Brew Co](https://wallbrew.com/)\n\nThis software is provided for free, public use as outlined in the [MIT License](https://github.com/Wall-Brew-Co/clj-xml/blob/master/LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwall-brew-co%2Fclj-xml","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwall-brew-co%2Fclj-xml","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwall-brew-co%2Fclj-xml/lists"}