1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
|
# Dompa
[](https://codescene.io/projects/72504)
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` directly from GitHub:
```clojure
{:deps {askonomm/dompa {:git/url "https://git.nmm.ee/asko/dompa.git"
:git/tag "v1.2.2"
:git/sha "94f5f3c21f8ad352e149b59ce3a5fa3ae98acb91"}}}
```
-----
## ✨ 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 "<div>hello <strong>world</strong></div>")
```
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 [...])
;;=> "<div>hello <strong>world</strong></div>"
```
-----
### 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")))])
;;=> <div>hello world</div>
```
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")
;;=> "<div>hello world</div>"
```
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)
;;=> "<ul><li>john</li><li>mike</li><li>jenna</li></ul>"
```
**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`).
|