2020--- @class xml-generator
2121local export = {}
2222
23+ --- @class XML.Children
24+ --- @field [ integer] XML.Node | string | fun (): XML.Node
25+
26+ --- @class XML.AttributeTable : XML.Children
27+ --- @field [ string] string | boolean | number
28+
2329--- @class XML.Node
30+ --- @operator call (XML.Children ): XML.Node
2431--- @field tag string
25- --- @field children ( XML.Node | string | fun (): XML.Node ) []
26- --- @field attributes { [string] : ( string | boolean ) }
32+ --- @field children XML.Children
33+ --- @field attributes XML.AttributeTable
2734
2835--- quotes are allowed in text, not in attributes
2936--- @param str string
@@ -62,7 +69,7 @@ function export.node_to_string(node)
6269 if type (v ) == " boolean" then
6370 if v then html = html .. " " .. k end
6471 else
65- html = html .. " " .. k .. " =\" " .. export .sanitize_attributes (v ) .. " \" "
72+ html = html .. " " .. k .. " =\" " .. export .sanitize_attributes (tostring ( v ) ) .. " \" "
6673 end
6774 end
6875
@@ -81,90 +88,84 @@ function export.node_to_string(node)
8188 return html
8289end
8390
84- --- @generic T
85- --- @param fn T
86- --- @return T
87- function export .declare_xml_generator (fn )
88- local tbl = setmetatable ({}, {
89- --- @param self table
90- --- @param tag_name string
91- __index = function (self , tag_name )
92- --- @param attributes { [string] : string , [integer] : (XML.Node | string | fun (): XML.Node ) } | string
93- --- @return table | fun ( children : ( XML.Node | string | fun (): XML.Node ) [] ): XML.Node
94- return function (attributes )
95- --- @type XML.Node
96- local node = {
97- tag = tag_name ,
98- children = {},
99- attributes = {}
100- }
101-
102- -- if we have a situation such as
103- --[[
104- tag "string"
105- ]]
106- --
107- -- then the content is the `string`
108- local tname = typename (attributes )
109- if tname ~= " table" and tname ~= " HTML.Node" then
110- node .attributes = attributes and { tostring (attributes ) } or {}
111- elseif tname == " XML.Node" then
112- --- local tag = div { p "hi" }
113- --- div(tag)
114- node .children = { attributes }
115- attributes = {}
116- else
117- node .attributes = attributes --[[ @as any]]
118- end
91+ --- @class XML.GeneratorTable
92+ --- @field [ string] fun (attributes : XML.AttributeTable | string | XML.Node ): XML.Node
93+
94+ --- @type XML.GeneratorTable
95+ export .generator_metatable = setmetatable ({}, {
96+ --- @param self XML.GeneratorTable
97+ --- @param tag_name string
98+ __index = function (self , tag_name )
99+ --- @param attributes { [string] : string , [integer] : (XML.Node | string | fun (): XML.Node ) } | string
100+ --- @return table | fun ( children : ( XML.Node | string | fun (): XML.Node ) [] ): XML.Node
101+ return function (attributes )
102+ --- @type XML.Node
103+ local node = {
104+ tag = tag_name ,
105+ children = {},
106+ attributes = {}
107+ }
119108
120- for i , v in ipairs (node .attributes ) do
121- if type (v ) == " function" then
122- export .declare_xml_generator (v )
123- v = coroutine.wrap (v )
124- for sub in v do
125- node .children [# node .children + 1 ] = sub
126- end
127- else
128- node .children [# node .children + 1 ] = v
129- end
109+ -- if we have a situation such as
110+ --[[
111+ tag "string"
112+ ]]
113+ --
114+ -- then the content is the `string`
115+ local tname = typename (attributes )
116+ if tname ~= " table" and tname ~= " HTML.Node" then
117+ node .attributes = attributes and { tostring (attributes ) } or {}
118+ elseif tname == " XML.Node" then
119+ --- local tag = div { p "hi" }
120+ --- div(tag)
121+ node .children = { attributes }
122+ attributes = {}
123+ else
124+ node .attributes = attributes --[[ @as any]]
125+ end
130126
131- node .attributes [i ] = nil
127+ for i , v in ipairs (node .attributes ) do
128+ if type (v ) == " function" then
129+ v = coroutine.wrap (v )
130+ for sub in v do
131+ node .children [# node .children + 1 ] = sub
132+ end
133+ else
134+ node .children [# node .children + 1 ] = v
132135 end
133136
134- return setmetatable ( node , {
135- __type = " XML.Node " ,
137+ node . attributes [ i ] = nil
138+ end
136139
137- __tostring = export .node_to_string ,
140+ return setmetatable (node , {
141+ __type = " XML.Node" ,
138142
139- --- @param self XML.Node
140- --- @param children (XML.Node | string | fun (): XML.Node ) []
141- __call = function (self , children )
142- if type (children ) ~= " table" then
143- children = { tostring (children ) }
144- end
143+ __tostring = export .node_to_string ,
145144
146- for _ , v in ipairs (children ) do
147- if type (v ) == " function" then
148- export .declare_xml_generator (v )
149- v = coroutine.wrap (v )
150- for sub in v do
151- self .children [# self .children + 1 ] = sub
152- end
153- else
154- self .children [# self .children + 1 ] = v
145+ --- @param self XML.Node
146+ --- @param children XML.Children
147+ __call = function (self , children )
148+ if type (children ) ~= " table" then
149+ children = { tostring (children ) }
150+ end
151+
152+ for _ , v in ipairs (children ) do
153+ if type (v ) == " function" then
154+ v = coroutine.wrap (v )
155+ for sub in v do
156+ self .children [# self .children + 1 ] = sub
155157 end
158+ else
159+ self .children [# self .children + 1 ] = v
156160 end
157-
158- return self
159161 end
160- })
161- end
162- end
163- })
164162
165- setfenv (fn , tbl )
166- return fn
167- end
163+ return self
164+ end
165+ })
166+ end
167+ end
168+ })
168169
169170--- Usage:
170171--[=[
@@ -188,48 +189,45 @@ end)
188189
189190```
190191]=]
191- --- @param ctx fun (): table
192- --- @return string
193- function export .generate_xml (ctx )
194- return export .node_to_string (export .declare_xml_generator (ctx )())
195- end
196-
197- --- @param ctx fun (): table
192+ --- @param ctx fun ( html : XML.GeneratorTable ): XML.Node
198193--- @return XML.Node
199- function export .generate_xml_node (ctx )
200- return export .declare_xml_generator (ctx )()
201- end
194+ function export .generate_node (ctx ) return ctx (export .generator_metatable ) end
195+
196+ --- @param ctx fun ( html : XML.GeneratorTable ): table
197+ --- @return string
198+ function export .generate (ctx ) return tostring (export .generate_node (ctx )) end
202199
203200--- Turns a lua table into an html table, recursively, with multiple levels of nesting
201+ --- @param xml XML.GeneratorTable
204202--- @param tbl table
205203--- @return XML.Node
206- function export .table ( tbl )
204+ function export .html_table ( xml , tbl )
207205 --- @diagnostic disable : undefined-global
208- return table {
206+ return xml . table {
209207 function ()
210208 local function getval (v )
211209 if type (v ) ~= " table" or (getmetatable (v ) or {}).__tostring then
212210 return tostring (v )
213211 end
214- return html_table (v )
212+ return export . html_table (xml , v )
215213 end
216214
217215 for i , v in ipairs (tbl ) do
218- yield (
219- tr {
220- td (tostring (i )),
221- td (getval (v )),
216+ coroutine. yield (
217+ xml . tr {
218+ xml . td (tostring (i )),
219+ xml . td (getval (v )),
222220 }
223221 )
224222
225223 tbl [i ] = nil
226224 end
227225
228226 for k , v in pairs (tbl ) do
229- yield (
230- tr {
231- td (tostring (k )),
232- td (getval (v )),
227+ coroutine. yield (
228+ xml . tr {
229+ xml . td (tostring (k )),
230+ xml . td (getval (v )),
233231 }
234232 )
235233 end
@@ -238,8 +236,6 @@ function export.table(tbl)
238236 --- @diagnostic enable : undefined-global
239237end
240238
241- export .declare_xml_generator (export .table )
242-
243239--- @alias OptionalStringCollection string | string[]
244240--- @param css { [OptionalStringCollection] : { [OptionalStringCollection] : (OptionalStringCollection ) } }
245241--- @return XML.Node
@@ -257,11 +253,7 @@ function export.style(css)
257253 css_str = css_str .. " }\n "
258254 end
259255
260- return export .generate_xml_node (function ()
261- --- @diagnostic disable : undefined-global
262- return style (css_str )
263- --- @diagnostic enable : undefined-global
264- end )
256+ return export .generate_node (function (xml ) return xml .style (css_str ) end )
265257end
266258
267259return export
0 commit comments