# Dompa A zero-dependency, runtime-agnostic HTML parser and builder for Clojure. Dompa aims to be a universal Clojure library, tested across: * Clojure * ClojureScript * Babashka ----- ## 🚀 Installation Add Dompa to your `deps.edn`: ```clojure {:deps {askonomm/dompa {:git/url "https://git.nmm.ee/asko/dompa.git" :git/tag "v1.2.3" :git/sha "a8696e088efedf310929bc4e01eded36d41414b1"}}} ``` ----- ## ✨ Usage ### Parsing and Creating HTML Dompa makes it simple to convert HTML strings into Clojure data structures and back. **1. Parse HTML to Nodes** Use `dompa.html/->nodes` to parse an HTML string into a vector of nodes. ```clojure (ns my.app (:require [dompa.html :as html])) (html/->nodes "
hello world
") ``` This produces a nested data structure: ```clojure [{:node/name :div :node/attrs {} :node/children [{:node/name :dompa/text :node/value "hello "} {:node/name :strong :node/attrs {} :node/children [{:node/name :dompa/text :node/value "world"}]}]}] ``` **2. Create HTML from Nodes** Use `dompa.nodes/->html` to convert the node structure back into an HTML string. ```clojure (ns my.app (:require [dompa.nodes :as nodes])) ;; ...using the nodes from the previous example (nodes/->html [...]) ;;=> "
hello world
" ``` ----- ### Traversing and Modifying Nodes Easily walk and transform the node tree with the `dompa.nodes/traverse` helper. ```clojure (ns my.app (:require [dompa.nodes :refer [traverse]]) (def nodes-data [...]) ; Your node structure (defn update-text-value [node] (if (= :dompa/text (:node/name node)) (assoc node :node/value "updated text") node)) (traverse nodes-data update-text-value) ``` The function you provide to `traverse` dictates the outcome for each node: * To **update a node**, return the modified node. * To **keep a node unchanged**, return the original node. * To **remove a node**, return `nil`. * To **replace a node with many nodes on the same level**, return a [fragment node](#fragment-nodes), which will be replaced by its children during HTML transformation. #### Zipping You can also use the `dompa.nodes/zip` function to create a [zipper](https://clojuredocs.org/clojure.zip/zipper) of a node, i.e: ```clojure (ns my.app (:require [dompa.nodes :as nodes] [clojure.zip :as zip]) (->> (nodes/zip {..node}) zip/down zip/node) ``` And of course you can use this in combination with the `traverse` method as well, since the `traverse` method always operates on a single node at a time, and the zipper always starts with a root node, the two complement each other well. ----- ### 🛠️ Building Nodes with the `$` Helper For a more idiomatic and concise way to build node structures, use the `$` helper from `dompa.nodes`. ```clojure (ns my.app (:require [dompa.nodes :refer [$]])) ;; A simple node ($ :button) ;; A node with attributes ($ :button {:class "some-btn"}) ;; A text node ($ "hello world") ;; Put it all together ($ :button {:class "some-btn"} "hello world") ``` All nodes (except text nodes) can be nested. Children are passed as the second argument (if no attributes) or the third argument (if attributes are present). #### Fragment nodes You can also use fragment nodes, which are nodes with a name of `:<>` and whose children replace themselves, for example: ```clojure (ns my.app (:require [dompa.nodes :refer [$ ->html]])) (->html [($ :button ($ :<> "hello " "world"))]) ;;=>
hello world
``` Note that the replacement happens only in the `->html` function during the transformation process, so if you use a fragment node in your node tree and wonder why it hasn't been replaced by its children, it's because of that. ----- ### ⚡️ Compile-Time HTML with `defhtml` The `defhtml` macro creates functions that build and render HTML at compile time for maximum performance. ```clojure (ns my.app (:require [dompa.nodes :refer [defhtml $]])) (defhtml hello-page [who] ($ :div "hello " who)) (hello-page "world") ;;=> "
hello world
" ``` It works seamlessly with standard Clojure functions like `map`: ```clojure (ns my.app (:require [dompa.nodes :refer [defhtml $]])) (def names ["john" "mike" "jenna"]) (defhtml name-list [] ($ :ul (map #($ :li %) names))) (name-list) ;;=> "" ``` #### Docstrings `defhtml` supports optional docstrings, just like `defn`: ```clojure (defhtml about-page "Renders the about page for a person." [who] ($ :div "Hello, " who "!")) ``` #### Composition Functions created with `defhtml` can be nested and composed with each other: ```clojure (defhtml greeting [who] ($ :span who)) (defhtml page [who] ($ :div (greeting who))) (page "world") ;;=> "
world
" ``` **Note for ClojureScript:** Remember to use `:refer-macros` instead of `:refer` when requiring `defhtml`. ----- ### ⚙️ Advanced: Lower-Level API Dompa also exposes the lower-level functions that power the parsing process. You can use these for more granular control: * `dompa.coordinates/compose`: Creates range positions of nodes from an HTML string. * `dompa.coordinates/unify`: Merges coordinates for the same block nodes. * `dompa.coordinates/->nodes`: Transforms coordinate data into a final node tree. * `dompa.html/->coordinates`: Transforms an HTML string into coordinate data (result of `dompa.coordinates/compose` and `dompa.coordinates/unify`).