<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-Hans-CN">
	<id>https://ac-wiki.cn/index.php?action=history&amp;feed=atom&amp;title=%E6%A8%A1%E5%9D%97%3AACandy</id>
	<title>模块:ACandy - 版本历史</title>
	<link rel="self" type="application/atom+xml" href="https://ac-wiki.cn/index.php?action=history&amp;feed=atom&amp;title=%E6%A8%A1%E5%9D%97%3AACandy"/>
	<link rel="alternate" type="text/html" href="https://ac-wiki.cn/index.php?title=%E6%A8%A1%E5%9D%97:ACandy&amp;action=history"/>
	<updated>2026-08-24T06:30:40Z</updated>
	<subtitle>本wiki上该页面的版本历史</subtitle>
	<generator>MediaWiki 1.45.3</generator>
	<entry>
		<id>https://ac-wiki.cn/index.php?title=%E6%A8%A1%E5%9D%97:ACandy&amp;diff=558&amp;oldid=prev</id>
		<title>天明：​创建页面，内容为“-- # ACandy  A module for building HTML. 一个用于构建HTML的模块。  Version requirement: Lua 5.1 or higher  GitHub: https://github.com/AmeroHan/ACandy MIT License Copyright (c) 2023 - 2024 AmeroHan   local acandy_submodules acandy_submodules = setmetatable({ load_classes = function () local getmt = getmetatable local setmt = setmetatable local tostring = tostring  local classes = {}  ---@class StringSymbol: Symbol ---@field getter fun(t: table): st…”</title>
		<link rel="alternate" type="text/html" href="https://ac-wiki.cn/index.php?title=%E6%A8%A1%E5%9D%97:ACandy&amp;diff=558&amp;oldid=prev"/>
		<updated>2025-12-24T11:10:50Z</updated>

		<summary type="html">&lt;p&gt;创建页面，内容为“--&lt;a href=&quot;#_ACandy_A_module_for_building_HTML._一个用于构建HTML的模块。_Version_requirement:_Lua_5.1_or_higher_GitHub:_https://github.com/AmeroHan/ACandy_MIT_License_Copyright_(c)_2023_-_2024_AmeroHan&quot;&gt;# ACandy  A module for building HTML. 一个用于构建HTML的模块。  Version requirement: Lua 5.1 or higher  GitHub: https://github.com/AmeroHan/ACandy MIT License Copyright (c) 2023 - 2024 AmeroHan &lt;/a&gt;  local acandy_submodules acandy_submodules = setmetatable({ load_classes = function () local getmt = getmetatable local setmt = setmetatable local tostring = tostring  local classes = {}  ---@class StringSymbol: Symbol ---@field getter fun(t: table): st…”&lt;/p&gt;
&lt;p&gt;&lt;b&gt;新页面&lt;/b&gt;&lt;/p&gt;&lt;div&gt;--[[&lt;br /&gt;
# ACandy&lt;br /&gt;
&lt;br /&gt;
A module for building HTML.&lt;br /&gt;
一个用于构建HTML的模块。&lt;br /&gt;
&lt;br /&gt;
Version requirement: Lua 5.1 or higher&lt;br /&gt;
&lt;br /&gt;
GitHub: https://github.com/AmeroHan/ACandy&lt;br /&gt;
MIT License&lt;br /&gt;
Copyright (c) 2023 - 2024 AmeroHan&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local acandy_submodules&lt;br /&gt;
acandy_submodules = setmetatable({&lt;br /&gt;
load_classes = function ()&lt;br /&gt;
local getmt = getmetatable&lt;br /&gt;
local setmt = setmetatable&lt;br /&gt;
local tostring = tostring&lt;br /&gt;
&lt;br /&gt;
local classes = {}&lt;br /&gt;
&lt;br /&gt;
---@class StringSymbol: Symbol&lt;br /&gt;
---@field getter fun(t: table): string&lt;br /&gt;
local SYM_STRING = {}&lt;br /&gt;
&lt;br /&gt;
function SYM_STRING.getter(t)&lt;br /&gt;
	return t[SYM_STRING]&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
classes.SYM_STRING = SYM_STRING&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
---@type {[metatable]: true, register: fun(self: self, mt: metatable): metatable}&lt;br /&gt;
local node_mts = setmt({&lt;br /&gt;
	---Register a metatable as a node metatable.&lt;br /&gt;
	register = function (self, mt)&lt;br /&gt;
		self[mt] = true&lt;br /&gt;
		return mt&lt;br /&gt;
	end,&lt;br /&gt;
}, { __mode = &amp;#039;k&amp;#039; })&lt;br /&gt;
---To judge when a table with `__tostring` is being serialized, whether or not&lt;br /&gt;
---escape the string. If the table is an acandy node, it should not be escaped,&lt;br /&gt;
---as the node itself will handle the escaping.&lt;br /&gt;
classes.node_mts = node_mts&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
---@class Comment&lt;br /&gt;
&lt;br /&gt;
local Comment_mt = node_mts:register {&lt;br /&gt;
	__tostring = function (self)&lt;br /&gt;
		return &amp;#039;&amp;lt;!--&amp;#039;..(self[SYM_STRING] or &amp;#039;&amp;#039;)..&amp;#039;--&amp;gt;&amp;#039;&lt;br /&gt;
	end,&lt;br /&gt;
}&lt;br /&gt;
---Comment.&lt;br /&gt;
---&lt;br /&gt;
---Defined at https://html.spec.whatwg.org/#comments&lt;br /&gt;
---@param content string?&lt;br /&gt;
---@return Comment&lt;br /&gt;
---@nodiscard&lt;br /&gt;
function classes.Comment(content)&lt;br /&gt;
	if content then&lt;br /&gt;
		-- the text must not start with the string &amp;quot;&amp;gt;&amp;quot; or &amp;quot;-&amp;gt;&amp;quot;,&lt;br /&gt;
		-- nor contain the strings &amp;quot;&amp;lt;!--&amp;quot;, &amp;quot;--&amp;gt;&amp;quot;, or &amp;quot;--!&amp;gt;&amp;quot;,&lt;br /&gt;
		-- nor end with the string &amp;quot;&amp;lt;!-&amp;quot;&lt;br /&gt;
		local s = &amp;#039;--&amp;#039;..content..&amp;#039;-&amp;#039;&lt;br /&gt;
		if&lt;br /&gt;
			s:find(&amp;#039;&amp;lt;!--&amp;#039;, 1, true)&lt;br /&gt;
			or s:find(&amp;#039;--&amp;gt;&amp;#039;, 1, true)&lt;br /&gt;
			or content:find(&amp;#039;--!&amp;gt;&amp;#039;, 1, true)&lt;br /&gt;
		then&lt;br /&gt;
			error(&amp;#039;invalid comment content: &amp;#039;..(&amp;#039;%q&amp;#039;):format(content), 2)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return setmt({ [SYM_STRING] = content }, Comment_mt)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
---@class Doctype&lt;br /&gt;
-- Specs:&lt;br /&gt;
--   HTML:&lt;br /&gt;
--     definition: https://html.spec.whatwg.org/#the-doctype&lt;br /&gt;
--     serialization: https://html.spec.whatwg.org/#serialising-html-fragments&lt;br /&gt;
--   XML: https://www.w3.org/TR/xml/#sec-prolog-dtd&lt;br /&gt;
-- TODO: support any doctype&lt;br /&gt;
&lt;br /&gt;
local Doctype_mt = node_mts:register {&lt;br /&gt;
	__tostring = function ()&lt;br /&gt;
		return &amp;#039;&amp;lt;!DOCTYPE html&amp;gt;&amp;#039;&lt;br /&gt;
	end,&lt;br /&gt;
}&lt;br /&gt;
classes.Doctype = {&lt;br /&gt;
	---HTML5 doctype shortcut.&lt;br /&gt;
	---@type Doctype&lt;br /&gt;
	HTML = setmetatable({}, Doctype_mt),&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
---@class Raw&lt;br /&gt;
---@operator concat(Raw): Raw&lt;br /&gt;
&lt;br /&gt;
local Raw_mt  ---@type metatable&lt;br /&gt;
Raw_mt = node_mts:register {&lt;br /&gt;
	__tostring = SYM_STRING.getter,&lt;br /&gt;
	__concat = function (left, right)&lt;br /&gt;
		if getmt(left) ~= Raw_mt or getmt(right) ~= Raw_mt then&lt;br /&gt;
			error(&amp;#039;Raw object can only be concatenated with another Raw object&amp;#039;, 2)&lt;br /&gt;
		end&lt;br /&gt;
		return setmt({ [SYM_STRING] = left[SYM_STRING]..right[SYM_STRING] }, Raw_mt)&lt;br /&gt;
	end,&lt;br /&gt;
	__newindex = function ()&lt;br /&gt;
		error(&amp;#039;Raw object is immutable&amp;#039;, 2)&lt;br /&gt;
	end,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
---Create a Raw object, which would not be encoded when converted to string.&lt;br /&gt;
---@param content any value to be converted to string by `tostring()`&lt;br /&gt;
---@return Raw&lt;br /&gt;
---@nodiscard&lt;br /&gt;
function classes.Raw(content)&lt;br /&gt;
	return setmt({ [SYM_STRING] = tostring(content) }, Raw_mt)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return classes&lt;br /&gt;
end,&lt;br /&gt;
load_utils = function ()&lt;br /&gt;
local utils = {}&lt;br /&gt;
&lt;br /&gt;
local pairs = pairs&lt;br /&gt;
local ipairs = ipairs&lt;br /&gt;
local s_gsub = string.gsub&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
---Shallow copy a table&amp;#039;s sequence part using `ipairs`.&lt;br /&gt;
---@generic T&lt;br /&gt;
---@param from T[]&lt;br /&gt;
---@param into T[]?&lt;br /&gt;
---@return T[]&lt;br /&gt;
function utils.copy_ipairs(from, into)&lt;br /&gt;
	into = into or {}&lt;br /&gt;
	for i, v in ipairs(from) do&lt;br /&gt;
		into[i] = v&lt;br /&gt;
	end&lt;br /&gt;
	return into&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
---Shallow copy a table using `pairs`.&lt;br /&gt;
---@param from table&lt;br /&gt;
---@param into table?&lt;br /&gt;
---@return table&lt;br /&gt;
function utils.copy_pairs(from, into)&lt;br /&gt;
	into = into or {}&lt;br /&gt;
	for k, v in pairs(from) do&lt;br /&gt;
		into[k] = v&lt;br /&gt;
	end&lt;br /&gt;
	return into&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
---Shallow copy a table without calling `__pairs` metamethod.&lt;br /&gt;
---@param from table&lt;br /&gt;
---@param into table?&lt;br /&gt;
---@return table&lt;br /&gt;
function utils.raw_shallow_copy(from, into)&lt;br /&gt;
	into = into or {}&lt;br /&gt;
	for k, v in next, from do&lt;br /&gt;
		into[k] = v&lt;br /&gt;
	end&lt;br /&gt;
	return into&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
---Apply `func` to each element of `...` and return a table.&lt;br /&gt;
---@generic TIn&lt;br /&gt;
---@generic TOut&lt;br /&gt;
---@param func fun(arg: TIn): TOut&lt;br /&gt;
---@vararg TIn&lt;br /&gt;
---@return TOut[]&lt;br /&gt;
function utils.map_varargs(func, ...)&lt;br /&gt;
	local n = select(&amp;#039;#&amp;#039;, ...)&lt;br /&gt;
	local t = { ... }&lt;br /&gt;
	for i = 1, n do&lt;br /&gt;
		t[i] = func(t[i])&lt;br /&gt;
	end&lt;br /&gt;
	return t&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
---@generic T&lt;br /&gt;
---@param list T[]&lt;br /&gt;
---@return {[T]: true}&lt;br /&gt;
function utils.list_to_bool_dict(list)&lt;br /&gt;
	local dict = {}&lt;br /&gt;
	for _, v in ipairs(list) do&lt;br /&gt;
		dict[v] = true&lt;br /&gt;
	end&lt;br /&gt;
	return dict&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local NBSP = &amp;#039;\194\160&amp;#039;&lt;br /&gt;
local ENTITY_ENCODE_MAP = {&lt;br /&gt;
	[&amp;#039;&amp;amp;&amp;#039;] = &amp;#039;&amp;amp;amp;&amp;#039;,&lt;br /&gt;
	[NBSP] = &amp;#039;&amp;amp;nbsp;&amp;#039;,&lt;br /&gt;
	[&amp;#039;&amp;quot;&amp;#039;] = &amp;#039;&amp;amp;quot;&amp;#039;,&lt;br /&gt;
	[&amp;#039;&amp;lt;&amp;#039;] = &amp;#039;&amp;amp;lt;&amp;#039;,&lt;br /&gt;
	[&amp;#039;&amp;gt;&amp;#039;] = &amp;#039;&amp;amp;gt;&amp;#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
---Replace `&amp;amp;`, NBSP, `&amp;quot;`, `&amp;lt;`, `&amp;gt;` with entities.&lt;br /&gt;
---@param str string | number&lt;br /&gt;
---@return string&lt;br /&gt;
function utils.attr_encode(str)&lt;br /&gt;
	return (s_gsub(str, &amp;#039;\194?[\160&amp;amp;&amp;quot;&amp;lt;&amp;gt;]&amp;#039;, ENTITY_ENCODE_MAP))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
---Replace `&amp;amp;`, NBSP, `&amp;lt;`, `&amp;gt;` with entities.&lt;br /&gt;
---@param str string | number&lt;br /&gt;
---@return string&lt;br /&gt;
function utils.html_encode(str)&lt;br /&gt;
	return (s_gsub(str, &amp;#039;\194?[\160&amp;amp;&amp;lt;&amp;gt;]&amp;#039;, ENTITY_ENCODE_MAP))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local NON_CUSTOM_NAMES = {&lt;br /&gt;
	[&amp;#039;annotation-xml&amp;#039;] = true,&lt;br /&gt;
	[&amp;#039;color-profile&amp;#039;] = true,&lt;br /&gt;
	[&amp;#039;font-face&amp;#039;] = true,&lt;br /&gt;
	[&amp;#039;font-face-src&amp;#039;] = true,&lt;br /&gt;
	[&amp;#039;font-face-uri&amp;#039;] = true,&lt;br /&gt;
	[&amp;#039;font-face-format&amp;#039;] = true,&lt;br /&gt;
	[&amp;#039;font-face-name&amp;#039;] = true,&lt;br /&gt;
	[&amp;#039;missing-glyph&amp;#039;] = true,&lt;br /&gt;
}&lt;br /&gt;
---defined at https://html.spec.whatwg.org/#prod-pcenchar&lt;br /&gt;
local PCEN_CHAR_RANGES = {&lt;br /&gt;
	{ 0x2D,    0x2E },  -- &amp;#039;-&amp;#039;, &amp;#039;.&amp;#039;&lt;br /&gt;
	{ 0x30,    0x39 },  -- 0-9&lt;br /&gt;
	{ 0x5F,    0x5F },  -- &amp;#039;_&amp;#039;&lt;br /&gt;
	{ 0x61,    0x7A },  -- a-z&lt;br /&gt;
	{ 0xB7,    0xB7 },&lt;br /&gt;
	{ 0xC0,    0xD6 },&lt;br /&gt;
	{ 0xD8,    0xF6 },&lt;br /&gt;
	{ 0xF8,    0x37D },&lt;br /&gt;
	{ 0x37F,   0x1FFF },&lt;br /&gt;
	{ 0x200C,  0x200D },&lt;br /&gt;
	{ 0x203F,  0x2040 },&lt;br /&gt;
	{ 0x2070,  0x218F },&lt;br /&gt;
	{ 0x2C00,  0x2FEF },&lt;br /&gt;
	{ 0x3001,  0xD7FF },&lt;br /&gt;
	{ 0xF900,  0xFDCF },&lt;br /&gt;
	{ 0xFDF0,  0xFFFD },&lt;br /&gt;
	{ 0x10000, 0xEFFFF },&lt;br /&gt;
}&lt;br /&gt;
---@param code_point integer&lt;br /&gt;
---@return boolean&lt;br /&gt;
local function is_pcen_char_code(code_point)&lt;br /&gt;
	for _, range in ipairs(PCEN_CHAR_RANGES) do&lt;br /&gt;
		if code_point &amp;gt;= range[1] and code_point &amp;lt;= range[2] then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return false&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
---Return truthy value when `name` is a valid HTML tag name, otherwise falsy value.&lt;br /&gt;
---&lt;br /&gt;
---Defined at:&lt;br /&gt;
---- https://html.spec.whatwg.org/#syntax-tag-name&lt;br /&gt;
---- https://html.spec.whatwg.org/#valid-custom-element-name&lt;br /&gt;
---@param name any&lt;br /&gt;
---@return any&lt;br /&gt;
function utils.is_html_tag_name(name)&lt;br /&gt;
	if type(name) ~= &amp;#039;string&amp;#039; then&lt;br /&gt;
		return false&lt;br /&gt;
	elseif name:find(&amp;#039;^%w+$&amp;#039;) then&lt;br /&gt;
		return true&lt;br /&gt;
	elseif NON_CUSTOM_NAMES[name:lower()] then&lt;br /&gt;
		return true&lt;br /&gt;
	end&lt;br /&gt;
	---@cast name string&lt;br /&gt;
	local subs1, subs2 = name:match(&amp;#039;^%l(.*)%-(.*)$&amp;#039;)&lt;br /&gt;
	if not subs1 then&lt;br /&gt;
		return false&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	---@param s string&lt;br /&gt;
	---@return boolean&lt;br /&gt;
	local function validate(s)&lt;br /&gt;
		if s:find(&amp;#039;^[%-%.%d_%l]*$&amp;#039;) then&lt;br /&gt;
			return true&lt;br /&gt;
		end&lt;br /&gt;
		for cp in mw.ustring.gcodepoint(s) do&lt;br /&gt;
			if not is_pcen_char_code(cp) then&lt;br /&gt;
				return false&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		return true&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return validate(subs1) and validate(subs2)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
---Return truthy value when `name` is a valid HTML attribute name, otherwise falsy value.&lt;br /&gt;
---&lt;br /&gt;
---Defined at:&lt;br /&gt;
---- https://html.spec.whatwg.org/#syntax-attribute-name&lt;br /&gt;
---@param name any&lt;br /&gt;
---@return any&lt;br /&gt;
function utils.is_html_attr_name(name)&lt;br /&gt;
	if type(name) ~= &amp;#039;string&amp;#039; then&lt;br /&gt;
		return false&lt;br /&gt;
	elseif name:find(&amp;#039;[%z\1-\31\127 &amp;quot;\&amp;#039;&amp;gt;/=]&amp;#039;) then&lt;br /&gt;
		return false&lt;br /&gt;
	end&lt;br /&gt;
	return true&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
---@param str string&lt;br /&gt;
---@return table&lt;br /&gt;
function utils.parse_shorthand_attrs(str)&lt;br /&gt;
	-- parse id&lt;br /&gt;
	local id = nil&lt;br /&gt;
	str = s_gsub(str, &amp;#039;#([^%s#]*)&amp;#039;, function (s)&lt;br /&gt;
		if s == &amp;#039;&amp;#039; then&lt;br /&gt;
			error(&amp;#039;empty id found in &amp;#039;..(&amp;#039;%q&amp;#039;):format(str), 4)&lt;br /&gt;
		end&lt;br /&gt;
		if id then&lt;br /&gt;
			error(&amp;#039;two or more ids found in &amp;#039;..(&amp;#039;%q&amp;#039;):format(str), 4)&lt;br /&gt;
		end&lt;br /&gt;
		id = s&lt;br /&gt;
		return &amp;#039;&amp;#039;&lt;br /&gt;
	end)&lt;br /&gt;
&lt;br /&gt;
	-- parse class&lt;br /&gt;
	local class = str:gsub(&amp;#039;^%s*(.-)%s*$&amp;#039;, &amp;#039;%1&amp;#039;):gsub(&amp;#039;%s+&amp;#039;, &amp;#039; &amp;#039;)&lt;br /&gt;
&lt;br /&gt;
	return {&lt;br /&gt;
		id = id,&lt;br /&gt;
		class = class ~= &amp;#039;&amp;#039; and class or nil,&lt;br /&gt;
	}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return utils&lt;br /&gt;
end,&lt;br /&gt;
load_config = function ()&lt;br /&gt;
-- ref: https://html.spec.whatwg.org/#elements-2&lt;br /&gt;
&lt;br /&gt;
local DEFAULT_CONFIG = {&lt;br /&gt;
	html = {&lt;br /&gt;
		void_elements = {&lt;br /&gt;
			&amp;#039;area&amp;#039;,&lt;br /&gt;
			&amp;#039;base&amp;#039;, &amp;#039;br&amp;#039;,&lt;br /&gt;
			&amp;#039;col&amp;#039;,&lt;br /&gt;
			&amp;#039;embed&amp;#039;,&lt;br /&gt;
			&amp;#039;hr&amp;#039;,&lt;br /&gt;
			&amp;#039;img&amp;#039;,&lt;br /&gt;
			&amp;#039;input&amp;#039;,&lt;br /&gt;
			&amp;#039;link&amp;#039;,&lt;br /&gt;
			&amp;#039;meta&amp;#039;,&lt;br /&gt;
			&amp;#039;param&amp;#039;,&lt;br /&gt;
			&amp;#039;source&amp;#039;,&lt;br /&gt;
			&amp;#039;track&amp;#039;,&lt;br /&gt;
			&amp;#039;wbr&amp;#039;,&lt;br /&gt;
		},&lt;br /&gt;
		raw_text_elements = {&lt;br /&gt;
			&amp;#039;script&amp;#039;, &amp;#039;style&amp;#039;,&lt;br /&gt;
		},&lt;br /&gt;
	},&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local utils = acandy_submodules.utils&lt;br /&gt;
&lt;br /&gt;
local module = {}&lt;br /&gt;
&lt;br /&gt;
---ACandy configuration.&lt;br /&gt;
---@class Config&lt;br /&gt;
---@field void_elements {[string]: boolean}&lt;br /&gt;
---@field raw_text_elements {[string]: boolean}&lt;br /&gt;
&lt;br /&gt;
---@param output_type &amp;#039;html&amp;#039;&lt;br /&gt;
---@param modify_config fun(config: Config)?&lt;br /&gt;
---@nodiscard&lt;br /&gt;
function module.parse_config(output_type, modify_config)&lt;br /&gt;
	local base_config = DEFAULT_CONFIG[output_type]&lt;br /&gt;
	assert(base_config, &amp;#039;unsupported output type: &amp;#039;..output_type)&lt;br /&gt;
&lt;br /&gt;
	local config = {&lt;br /&gt;
		void_elements = utils.list_to_bool_dict(base_config.void_elements),&lt;br /&gt;
		raw_text_elements = utils.list_to_bool_dict(base_config.raw_text_elements),&lt;br /&gt;
	}&lt;br /&gt;
	if modify_config then&lt;br /&gt;
		modify_config(config)&lt;br /&gt;
	end&lt;br /&gt;
	return config.void_elements, config.raw_text_elements&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return module&lt;br /&gt;
end,&lt;br /&gt;
}, { __index = function(t, module_name)&lt;br /&gt;
	local loader_key = &amp;#039;load_&amp;#039;..module_name&lt;br /&gt;
	local mod = t[loader_key]()&lt;br /&gt;
	t[module_name] = mod&lt;br /&gt;
	t[loader_key] = nil&lt;br /&gt;
	return mod&lt;br /&gt;
end&lt;br /&gt;
})&lt;br /&gt;
&lt;br /&gt;
local type = type&lt;br /&gt;
local pairs = pairs&lt;br /&gt;
local getmt = getmetatable&lt;br /&gt;
local setmt = setmetatable&lt;br /&gt;
local assert = assert&lt;br /&gt;
local concat = table.concat&lt;br /&gt;
local ipairs = ipairs&lt;br /&gt;
local rawget = rawget&lt;br /&gt;
local rawset = rawset&lt;br /&gt;
local tostring = tostring&lt;br /&gt;
&lt;br /&gt;
local utils = acandy_submodules.utils&lt;br /&gt;
local classes = acandy_submodules.classes&lt;br /&gt;
local node_mts = classes.node_mts&lt;br /&gt;
local config_module = acandy_submodules.config&lt;br /&gt;
&lt;br /&gt;
---@class Symbol&lt;br /&gt;
&lt;br /&gt;
local SYM_STRING = classes.SYM_STRING&lt;br /&gt;
local SYM_ATTR_MAP = {}  ---@type Symbol&lt;br /&gt;
local SYM_CHILDREN = {}  ---@type Symbol&lt;br /&gt;
local SYM_TAG_NAME = {}  ---@type Symbol&lt;br /&gt;
&lt;br /&gt;
local KEY_LIST_LIKE = &amp;#039;__acandy_list_like&amp;#039;&lt;br /&gt;
local KEY_TABLE_LIKE = &amp;#039;__acandy_table_like&amp;#039;&lt;br /&gt;
&lt;br /&gt;
---@param v any&lt;br /&gt;
---@return integer 1: list-like, 2: table-like, 0: others&lt;br /&gt;
---@nodiscard&lt;br /&gt;
local function container_level_of(v)&lt;br /&gt;
	local mt = getmt(v)&lt;br /&gt;
	if not mt then&lt;br /&gt;
		return type(v) == &amp;#039;table&amp;#039; and 2 or 0&lt;br /&gt;
	elseif mt[KEY_TABLE_LIKE] == true then&lt;br /&gt;
		return 2&lt;br /&gt;
	elseif mt[KEY_LIST_LIKE] == true then&lt;br /&gt;
		return 1&lt;br /&gt;
	end&lt;br /&gt;
	return 0&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
---Append the serialized string of the Fragment to `buff`.&lt;br /&gt;
---Use len to avoid calling `#buff` repeatedly. This improves performance by&lt;br /&gt;
---~1/3.&lt;br /&gt;
---@param buff table&lt;br /&gt;
---@param frag table&lt;br /&gt;
---@param buff_len? integer length of `buff`, used to optimize performance.&lt;br /&gt;
---@param no_encode? boolean true to prevent encoding strings, e.g. when in &amp;lt;script&amp;gt;.&lt;br /&gt;
local function extend_str_buff_with_frag(buff, frag, buff_len, no_encode)&lt;br /&gt;
	if #frag == 0 then return end&lt;br /&gt;
	buff_len = buff_len or #buff&lt;br /&gt;
&lt;br /&gt;
	local function append_serialized(node)&lt;br /&gt;
		local node_type = type(node)&lt;br /&gt;
		if container_level_of(node) &amp;gt;= 1 then  -- Fragment, list&lt;br /&gt;
			for _, child_node in ipairs(node) do&lt;br /&gt;
				append_serialized(child_node)&lt;br /&gt;
			end&lt;br /&gt;
		elseif node_type == &amp;#039;function&amp;#039; then&lt;br /&gt;
			append_serialized(node())&lt;br /&gt;
		elseif node_type == &amp;#039;string&amp;#039; then&lt;br /&gt;
			buff_len = buff_len + 1&lt;br /&gt;
			buff[buff_len] = no_encode and node or utils.html_encode(node)&lt;br /&gt;
		else  -- others: Raw, Element, boolean, number&lt;br /&gt;
			local str = tostring(node)&lt;br /&gt;
			if not (node_mts[getmt(node)] or no_encode) then&lt;br /&gt;
				str = utils.html_encode(str)&lt;br /&gt;
			end&lt;br /&gt;
			buff_len = buff_len + 1&lt;br /&gt;
			buff[buff_len] = str&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	append_serialized(frag)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
---@param buff string[]&lt;br /&gt;
---@param attr_map {[string]: string | number | boolean}&lt;br /&gt;
---@param buff_len integer?&lt;br /&gt;
---@return integer new_buff_len&lt;br /&gt;
local function extend_str_buff_with_attrs(buff, attr_map, buff_len)&lt;br /&gt;
	buff_len = buff_len or #buff&lt;br /&gt;
	for k, v in pairs(attr_map) do&lt;br /&gt;
		if v == true then&lt;br /&gt;
			-- boolean attributes&lt;br /&gt;
			-- https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes#boolean_attributes&lt;br /&gt;
			buff_len = buff_len + 2&lt;br /&gt;
			buff[buff_len - 1] = &amp;#039; &amp;#039;&lt;br /&gt;
			buff[buff_len] = k&lt;br /&gt;
		elseif v then  -- exclude the case `v == false`&lt;br /&gt;
			buff_len = buff_len + 5&lt;br /&gt;
			buff[buff_len - 4] = &amp;#039; &amp;#039;&lt;br /&gt;
			buff[buff_len - 3] = k&lt;br /&gt;
			buff[buff_len - 2] = &amp;#039;=&amp;quot;&amp;#039;&lt;br /&gt;
			buff[buff_len - 1] = utils.attr_encode(tostring(v))&lt;br /&gt;
			buff[buff_len] = &amp;#039;&amp;quot;&amp;#039;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return buff_len&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local Fragment_mt = node_mts:register {&lt;br /&gt;
	---Flat and concat the Fragment, returns string.&lt;br /&gt;
	---@param self Fragment&lt;br /&gt;
	---@return string&lt;br /&gt;
	__tostring = function (self)&lt;br /&gt;
		if #self == 0 then return &amp;#039;&amp;#039; end&lt;br /&gt;
&lt;br /&gt;
		local buff = {}&lt;br /&gt;
		extend_str_buff_with_frag(buff, self, 0)&lt;br /&gt;
		return concat(buff)&lt;br /&gt;
	end,&lt;br /&gt;
	---ACandy fragment.&lt;br /&gt;
	---@class Fragment&amp;lt;T&amp;gt;: {[integer]: T}&lt;br /&gt;
	__index = {&lt;br /&gt;
		concat = table.concat,&lt;br /&gt;
		insert = table.insert,&lt;br /&gt;
		---@version &amp;lt;5.1&lt;br /&gt;
		---@diagnostic disable-next-line: deprecated&lt;br /&gt;
		maxn = table.maxn,  -- Lua 5.1 only&lt;br /&gt;
		move = table.move,&lt;br /&gt;
		remove = table.remove,&lt;br /&gt;
		sort = table.sort,&lt;br /&gt;
		unpack = table.unpack or unpack,  ---@diagnostic disable-line: deprecated&lt;br /&gt;
	},&lt;br /&gt;
	[KEY_LIST_LIKE] = true,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
---Constructor of Fragment.&lt;br /&gt;
---@param children any?&lt;br /&gt;
---@return Fragment&lt;br /&gt;
---@nodiscard&lt;br /&gt;
local function Fragment(children)&lt;br /&gt;
	if container_level_of(children) &amp;gt;= 1 then&lt;br /&gt;
		return setmt(utils.copy_ipairs(children), Fragment_mt)&lt;br /&gt;
	end&lt;br /&gt;
	return setmt({ children }, Fragment_mt)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
---@param breadcrumb Breadcrumb&lt;br /&gt;
---@return string[] tag_names&lt;br /&gt;
---@return ({[string]: string | number | boolean} | nil)[] attr_maps&lt;br /&gt;
---@nodiscard&lt;br /&gt;
local function clone_breadcrumb_tags_and_attrs(breadcrumb)&lt;br /&gt;
	local new_tag_names = {}&lt;br /&gt;
	local new_attr_maps = {}&lt;br /&gt;
	local orig_attr_maps = breadcrumb[SYM_ATTR_MAP]&lt;br /&gt;
	for i, tag_name in ipairs(breadcrumb[SYM_TAG_NAME]) do&lt;br /&gt;
		new_tag_names[i] = tag_name&lt;br /&gt;
		new_attr_maps[i] = orig_attr_maps[i]&lt;br /&gt;
	end&lt;br /&gt;
	return new_tag_names, new_attr_maps&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
---@param breadcrumb1 Breadcrumb&lt;br /&gt;
---@param breadcrumb2 Breadcrumb&lt;br /&gt;
---@return string[] tag_names&lt;br /&gt;
---@return ({[string]: string | number | boolean} | nil)[] attr_maps&lt;br /&gt;
---@nodiscard&lt;br /&gt;
local function connect_breadcrumbs(breadcrumb1, breadcrumb2)&lt;br /&gt;
	local new_tag_names, new_attr_maps = clone_breadcrumb_tags_and_attrs(breadcrumb1)&lt;br /&gt;
	local len = #new_tag_names&lt;br /&gt;
	local attr_maps2 = breadcrumb2[SYM_ATTR_MAP]&lt;br /&gt;
	for i, tag_name in ipairs(breadcrumb2[SYM_TAG_NAME]) do&lt;br /&gt;
		new_tag_names[len + i] = tag_name&lt;br /&gt;
		new_attr_maps[len + i] = attr_maps2[i]&lt;br /&gt;
	end&lt;br /&gt;
	return new_tag_names, new_attr_maps&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
---@param self Breadcrumb&lt;br /&gt;
---@return string&lt;br /&gt;
---@nodiscard&lt;br /&gt;
local function breadcrumb_to_string(self)&lt;br /&gt;
	local tag_names = self[SYM_TAG_NAME]&lt;br /&gt;
	local attr_maps = self[SYM_ATTR_MAP]&lt;br /&gt;
	local result = {}&lt;br /&gt;
&lt;br /&gt;
	for i, tag_name in ipairs(tag_names) do&lt;br /&gt;
		result[#result+1] = &amp;#039;&amp;gt;&amp;lt;&amp;#039;&lt;br /&gt;
		result[#result+1] = tag_name&lt;br /&gt;
		if attr_maps[i] then&lt;br /&gt;
			extend_str_buff_with_attrs(result, attr_maps[i])&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	result[1] = &amp;#039;&amp;lt;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
	for i = #tag_names, 1, -1 do&lt;br /&gt;
		result[#result+1] = &amp;#039;&amp;gt;&amp;lt;/&amp;#039;&lt;br /&gt;
		result[#result+1] = tag_names[i]&lt;br /&gt;
	end&lt;br /&gt;
	result[#result+1] = &amp;#039;&amp;gt;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
	return concat(result)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function ErrorEmitter(msg, level)&lt;br /&gt;
	return function ()&lt;br /&gt;
		error(msg, level)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local error_emitters = {&lt;br /&gt;
	unbuilt_elem_index = ErrorEmitter(&amp;#039;attempt to access properties of a unbuilt element&amp;#039;, 2),&lt;br /&gt;
	unbuilt_elem_newindex = ErrorEmitter(&amp;#039;attempt to assign properties of a unbuilt element&amp;#039;, 2),&lt;br /&gt;
	built_elem_div = ErrorEmitter(&amp;#039;attempt to perform division on a built element&amp;#039;, 2),&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
---@param output_type &amp;#039;html&amp;#039;&lt;br /&gt;
---@param modify_config fun(config: Config)?&lt;br /&gt;
---@nodiscard&lt;br /&gt;
local function ACandy(output_type, modify_config)&lt;br /&gt;
	local void_elems, raw_text_elems = config_module.parse_config(output_type, modify_config)&lt;br /&gt;
&lt;br /&gt;
	---A BareElement is an Element without any properties except tag name, e.g.,&lt;br /&gt;
	---`acandy.div`. It is immutable and can be cached and reused.&lt;br /&gt;
	---Indexing a BareElement would return a BuildingElement, and calling it would&lt;br /&gt;
	---return a BuiltElement. Both methods would not change the element itself.&lt;br /&gt;
	---&lt;br /&gt;
	---Example:&lt;br /&gt;
	---```lua&lt;br /&gt;
	---local bare_div = a.div&lt;br /&gt;
	---```&lt;br /&gt;
	---@class BareElement&lt;br /&gt;
	---@operator div(BareElement | BuildingElement | Breadcrumb): Breadcrumb&lt;br /&gt;
	---@operator div(any): BuiltElement&lt;br /&gt;
	---@overload fun(props: any): BuiltElement&lt;br /&gt;
	---@field [string | {[string]: string | number | boolean}] BuildingElement&lt;br /&gt;
&lt;br /&gt;
	---A BuildingElement is an Element derived from attribute shorthand syntax. The&lt;br /&gt;
	---shorthand is a string of id and space-separated class names, and the syntax&lt;br /&gt;
	---is to index the BareElement with a shorthand string, i.e. to put it inside&lt;br /&gt;
	---the brackets followed after the tag name, e.g. `acandy.div[&amp;#039;#id cls1 cls2&amp;#039;]`.&lt;br /&gt;
	---&lt;br /&gt;
	---Example:&lt;br /&gt;
	---```lua&lt;br /&gt;
	---local building_div = a.div[&amp;#039;#id cls1 cls2&amp;#039;]&lt;br /&gt;
	---```&lt;br /&gt;
	---&lt;br /&gt;
	---Similar to BareElements, a BuildingElement can be called to get a&lt;br /&gt;
	---BuiltElement with properties set.&lt;br /&gt;
	---@class BuildingElement&lt;br /&gt;
	---@operator div(BareElement | BuildingElement | Breadcrumb): Breadcrumb&lt;br /&gt;
	---@operator div(any): BuiltElement&lt;br /&gt;
	---@overload fun(props: any): BuiltElement&lt;br /&gt;
&lt;br /&gt;
	---A BuiltElement is an Element derived from a BareElement or a BuildingElement&lt;br /&gt;
	---by calling it, which would return the BuiltElement with properties set.&lt;br /&gt;
	---&lt;br /&gt;
	---```lua&lt;br /&gt;
	---local built_pre1 = a.pre {&lt;br /&gt;
	---	class = &amp;#039;lang-lua&amp;#039;;&lt;br /&gt;
	---	&amp;quot;print(&amp;#039;Hello, ACandy!&amp;#039;)&amp;quot;,&lt;br /&gt;
	---}&lt;br /&gt;
	---&lt;br /&gt;
	---local built_pre2 = a.pre[&amp;#039;lang-lua&amp;#039;] &amp;quot;print(&amp;#039;Hello, ACandy!&amp;#039;)&amp;quot;&lt;br /&gt;
	---```&lt;br /&gt;
	---&lt;br /&gt;
	---Although named &amp;quot;Built&amp;quot;, it is still mutable. Its properties can be changed by&lt;br /&gt;
	---assigning.&lt;br /&gt;
	---@class BuiltElement&lt;br /&gt;
	---@field tag_name string&lt;br /&gt;
	---@field attributes {[string]: string | number | boolean}&lt;br /&gt;
	---@field children Fragment?&lt;br /&gt;
	---@field [string] string | number | boolean attribute value&lt;br /&gt;
	---@field [number] any child node&lt;br /&gt;
&lt;br /&gt;
	---@class Breadcrumb&lt;br /&gt;
	---@operator div(BareElement | BuildingElement | Breadcrumb): Breadcrumb&lt;br /&gt;
	---@operator div(any): BuiltElement&lt;br /&gt;
	---@overload fun(props: any): BuiltElement&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
	local BareElement_mt  ---@type metatable&lt;br /&gt;
	local BuiltElement_mt  ---@type metatable&lt;br /&gt;
	local BuildingElement_mt  ---@type metatable&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
	---@param tag_name string&lt;br /&gt;
	---@return BareElement&lt;br /&gt;
	---@nodiscard&lt;br /&gt;
	local function BareElement(tag_name)&lt;br /&gt;
		local str&lt;br /&gt;
		if void_elems[tag_name] then&lt;br /&gt;
			str = &amp;#039;&amp;lt;&amp;#039;..tag_name..&amp;#039;&amp;gt;&amp;#039;&lt;br /&gt;
		else&lt;br /&gt;
			str = &amp;#039;&amp;lt;&amp;#039;..tag_name..&amp;#039;&amp;gt;&amp;lt;/&amp;#039;..tag_name..&amp;#039;&amp;gt;&amp;#039;&lt;br /&gt;
		end&lt;br /&gt;
		local elem = {&lt;br /&gt;
			[SYM_TAG_NAME] = tag_name,  ---@type string&lt;br /&gt;
			[SYM_STRING] = str,  ---@type string&lt;br /&gt;
		}&lt;br /&gt;
		return setmt(elem, BareElement_mt)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
	---@param tag_name string&lt;br /&gt;
	---@param attr_map {[string]: string | number | boolean}&lt;br /&gt;
	---@return BuildingElement&lt;br /&gt;
	---@nodiscard&lt;br /&gt;
	local function BuildingElement(tag_name, attr_map)&lt;br /&gt;
		local elem = {&lt;br /&gt;
			[SYM_TAG_NAME] = tag_name,&lt;br /&gt;
			[SYM_ATTR_MAP] = attr_map,&lt;br /&gt;
			[SYM_CHILDREN] = not void_elems[tag_name] and {} or nil,&lt;br /&gt;
		}&lt;br /&gt;
		return setmt(elem, BuildingElement_mt)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
	---@param tag_name string&lt;br /&gt;
	---@param attr_map {[string]: string | number | boolean}&lt;br /&gt;
	---@param children? any[]&lt;br /&gt;
	---@return BuiltElement&lt;br /&gt;
	---@nodiscard&lt;br /&gt;
	local function BuiltElement(tag_name, attr_map, children)&lt;br /&gt;
		assert(not (void_elems[tag_name] and children), &amp;#039;void elements cannot have children&amp;#039;)&lt;br /&gt;
		assert(void_elems[tag_name] or type(children) == &amp;#039;table&amp;#039;, &amp;#039;non-void elements must have children&amp;#039;)&lt;br /&gt;
		local elem = {&lt;br /&gt;
			[SYM_TAG_NAME] = tag_name,&lt;br /&gt;
			[SYM_ATTR_MAP] = attr_map,&lt;br /&gt;
			[SYM_CHILDREN] = children,&lt;br /&gt;
		}&lt;br /&gt;
		return setmt(elem, BuiltElement_mt)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
	---Convert the object into HTML code.&lt;br /&gt;
	---@param self BuildingElement | BuiltElement&lt;br /&gt;
	---@return string&lt;br /&gt;
	---@nodiscard&lt;br /&gt;
	local function elem_to_string(self)&lt;br /&gt;
		local tag_name = self[SYM_TAG_NAME]&lt;br /&gt;
&lt;br /&gt;
		-- format open tag&lt;br /&gt;
		local result = { &amp;#039;&amp;lt;&amp;#039;, tag_name }&lt;br /&gt;
		extend_str_buff_with_attrs(result, self[SYM_ATTR_MAP])&lt;br /&gt;
		result[#result+1] = &amp;#039;&amp;gt;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
		-- return without children or close tag when being a void element&lt;br /&gt;
		-- void element: https://developer.mozilla.org/en-US/docs/Glossary/Void_element&lt;br /&gt;
		if void_elems[tag_name] then&lt;br /&gt;
			return concat(result)&lt;br /&gt;
		end&lt;br /&gt;
&lt;br /&gt;
		-- format children&lt;br /&gt;
		extend_str_buff_with_frag(result, self[SYM_CHILDREN], nil, raw_text_elems[tag_name])&lt;br /&gt;
		-- format close tag&lt;br /&gt;
		result[#result+1] = &amp;#039;&amp;lt;/&amp;#039;&lt;br /&gt;
		result[#result+1] = tag_name&lt;br /&gt;
		result[#result+1] = &amp;#039;&amp;gt;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
		return concat(result)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
	---Return tag name, attribute or child node depending on the key.&lt;br /&gt;
	---@param self BuiltElement&lt;br /&gt;
	---@param key string | number&lt;br /&gt;
	---@nodiscard&lt;br /&gt;
	local function get_elem_prop(self, key)&lt;br /&gt;
		if key == &amp;#039;tag_name&amp;#039; then&lt;br /&gt;
			return self[SYM_TAG_NAME]&lt;br /&gt;
		elseif key == &amp;#039;children&amp;#039; then&lt;br /&gt;
			local children = rawget(self, SYM_CHILDREN)&lt;br /&gt;
			return children and setmt(children, Fragment_mt)&lt;br /&gt;
		elseif key == &amp;#039;attributes&amp;#039; then&lt;br /&gt;
			return self[SYM_ATTR_MAP]&lt;br /&gt;
		elseif type(key) == &amp;#039;string&amp;#039; then&lt;br /&gt;
			-- e.g. `elem.class`&lt;br /&gt;
			return self[SYM_ATTR_MAP][key]&lt;br /&gt;
		elseif type(key) == &amp;#039;number&amp;#039; then&lt;br /&gt;
			-- e.g. `elem[1]`&lt;br /&gt;
			local children = rawget(self, SYM_CHILDREN)&lt;br /&gt;
			return children and children[key]  -- no error for ipairs&lt;br /&gt;
		end&lt;br /&gt;
&lt;br /&gt;
		error(&amp;quot;element property key&amp;#039;s type is neither &amp;#039;string&amp;#039; nor &amp;#039;number&amp;#039;&amp;quot;, 2)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
	---@param self BareElement | BuildingElement | BuiltElement&lt;br /&gt;
	---@param props any&lt;br /&gt;
	---@return BuiltElement&lt;br /&gt;
	---@nodiscard&lt;br /&gt;
	local function new_built_elem_from_props(self, props)&lt;br /&gt;
		local tag_name = self[SYM_TAG_NAME]  ---@type any&lt;br /&gt;
		local base_attr_map = rawget(self, SYM_ATTR_MAP)&lt;br /&gt;
		local new_attr_map = base_attr_map and utils.copy_pairs(base_attr_map) or {}&lt;br /&gt;
		local container_level = container_level_of(props)&lt;br /&gt;
&lt;br /&gt;
		if void_elems[tag_name] then  -- void element, e.g. &amp;lt;br&amp;gt;, &amp;lt;img&amp;gt;&lt;br /&gt;
			if container_level == 2 then&lt;br /&gt;
				-- set attributes&lt;br /&gt;
				for k, v in pairs(props) do&lt;br /&gt;
					if type(k) == &amp;#039;string&amp;#039; then&lt;br /&gt;
						if not utils.is_html_attr_name(k) then&lt;br /&gt;
							error(&amp;#039;invalid attribute name: &amp;#039;..k, 2)&lt;br /&gt;
						end&lt;br /&gt;
						new_attr_map[k] = v&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			return BuiltElement(tag_name, new_attr_map)&lt;br /&gt;
		end&lt;br /&gt;
&lt;br /&gt;
		local new_children = {}&lt;br /&gt;
		if container_level == 2 then&lt;br /&gt;
			for k, v in pairs(props) do&lt;br /&gt;
				local t = type(k)&lt;br /&gt;
				if t == &amp;#039;number&amp;#039; then&lt;br /&gt;
					new_children[k] = v&lt;br /&gt;
				elseif t == &amp;#039;string&amp;#039; then&lt;br /&gt;
					if not utils.is_html_attr_name(k) then&lt;br /&gt;
						error(&amp;#039;invalid attribute name: &amp;#039;..k, 2)&lt;br /&gt;
					end&lt;br /&gt;
					new_attr_map[k] = v&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		elseif container_level == 1 then&lt;br /&gt;
			utils.copy_ipairs(props, new_children)&lt;br /&gt;
		else  -- treat as a single child&lt;br /&gt;
			new_children[1] = props&lt;br /&gt;
		end&lt;br /&gt;
&lt;br /&gt;
		return BuiltElement(tag_name, new_attr_map, new_children)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
	---Assign to tag name, attribute or child node depending on the key.&lt;br /&gt;
	---@param self BuildingElement | BuiltElement&lt;br /&gt;
	---@param key string | number&lt;br /&gt;
	---@param val any&lt;br /&gt;
	local function set_elem_prop(self, key, val)&lt;br /&gt;
		if key == &amp;#039;tag_name&amp;#039; then&lt;br /&gt;
			-- e.g., elem.tag_name = &amp;#039;div&amp;#039;&lt;br /&gt;
			if not utils.is_html_tag_name(val) then&lt;br /&gt;
				error(&amp;#039;invalid tag name: &amp;#039;..val, 2)&lt;br /&gt;
			end  ---@cast val string&lt;br /&gt;
&lt;br /&gt;
			val = val:lower()&lt;br /&gt;
&lt;br /&gt;
			if self[SYM_TAG_NAME] == val then return end&lt;br /&gt;
&lt;br /&gt;
			self[SYM_TAG_NAME] = val&lt;br /&gt;
			-- create/delete children table based on whether the element is a void element&lt;br /&gt;
			if void_elems[val] then&lt;br /&gt;
				rawset(self, SYM_CHILDREN, nil)&lt;br /&gt;
			elseif not rawget(self, SYM_CHILDREN) then&lt;br /&gt;
				rawset(self, SYM_CHILDREN, {})&lt;br /&gt;
			end&lt;br /&gt;
		elseif key == &amp;#039;children&amp;#039; or key == &amp;#039;attributes&amp;#039; then&lt;br /&gt;
			error(&amp;#039;attempt to replace the &amp;#039;..key..&amp;#039; table of the element&amp;#039;)&lt;br /&gt;
		elseif type(key) == &amp;#039;string&amp;#039; then&lt;br /&gt;
			-- e.g., elem.class = &amp;#039;content&amp;#039;&lt;br /&gt;
			if not utils.is_html_attr_name(key) then&lt;br /&gt;
				error(&amp;#039;invalid attribute name: &amp;#039;..key, 2)&lt;br /&gt;
			end&lt;br /&gt;
			self[SYM_ATTR_MAP][key] = val&lt;br /&gt;
		elseif type(key) == &amp;#039;number&amp;#039; then&lt;br /&gt;
			-- e.g., elem[1] = &amp;#039;some text&amp;#039;&lt;br /&gt;
			local children = rawget(self, SYM_CHILDREN)&lt;br /&gt;
			if not children then&lt;br /&gt;
				error(&amp;#039;attempt to assign child on a void element&amp;#039;, 2)&lt;br /&gt;
			end&lt;br /&gt;
			children[key] = val&lt;br /&gt;
		else&lt;br /&gt;
			error(&amp;quot;element property key&amp;#039;s type is neither &amp;#039;string&amp;#039; nor &amp;#039;number&amp;#039;&amp;quot;, 2)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
	local Breadcrumb_mt  ---@type metatable&lt;br /&gt;
&lt;br /&gt;
	---@param tag_names string[]&lt;br /&gt;
	---@param attr_maps ({[string]: string | number | boolean} | nil)[]&lt;br /&gt;
	---@return Breadcrumb&lt;br /&gt;
	---@nodiscard&lt;br /&gt;
	local function Breadcrumb(tag_names, attr_maps)&lt;br /&gt;
		return setmt({&lt;br /&gt;
			[SYM_TAG_NAME] = tag_names,&lt;br /&gt;
			[SYM_ATTR_MAP] = attr_maps,&lt;br /&gt;
		}, Breadcrumb_mt)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	---@param breadcrumb Breadcrumb&lt;br /&gt;
	---@return BuiltElement root_elem, BuiltElement leaf_elem&lt;br /&gt;
	---@nodiscard&lt;br /&gt;
	local function breadcrumb_to_built_elem(breadcrumb)&lt;br /&gt;
		local tag_names = breadcrumb[SYM_TAG_NAME]&lt;br /&gt;
		local attr_maps = breadcrumb[SYM_ATTR_MAP]&lt;br /&gt;
		local n = #tag_names&lt;br /&gt;
		local leaf_elem = BuiltElement(tag_names[n], attr_maps[n] or {}, {})&lt;br /&gt;
		local parent_elem = leaf_elem&lt;br /&gt;
		for i = n - 1, 1, -1 do&lt;br /&gt;
			parent_elem = BuiltElement(tag_names[i], attr_maps[i] or {}, { parent_elem })&lt;br /&gt;
		end&lt;br /&gt;
		return parent_elem, leaf_elem&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
	---@param left Breadcrumb&lt;br /&gt;
	---@param right any&lt;br /&gt;
	---@return Breadcrumb | BuiltElement&lt;br /&gt;
	local function breadcrumb_div(left, right)&lt;br /&gt;
		local right_mt = getmt(right)&lt;br /&gt;
&lt;br /&gt;
		if right_mt == BareElement_mt or right_mt == BuildingElement_mt then&lt;br /&gt;
			local right_tag_name = right[SYM_TAG_NAME]&lt;br /&gt;
			local right_attr_map = rawget(right, SYM_ATTR_MAP)&lt;br /&gt;
&lt;br /&gt;
			if void_elems[right_tag_name] then&lt;br /&gt;
				local root_elem, leaf_elem = breadcrumb_to_built_elem(left)&lt;br /&gt;
				leaf_elem[SYM_CHILDREN][1] = BuiltElement(right_tag_name, right_attr_map or {})&lt;br /&gt;
				return root_elem&lt;br /&gt;
			end&lt;br /&gt;
&lt;br /&gt;
			local new_tag_names, new_attr_maps = clone_breadcrumb_tags_and_attrs(left)&lt;br /&gt;
			local n = #new_tag_names + 1&lt;br /&gt;
			new_tag_names[n] = right_tag_name&lt;br /&gt;
			new_attr_maps[n] = right_attr_map&lt;br /&gt;
			return Breadcrumb(new_tag_names, new_attr_maps)&lt;br /&gt;
		elseif right_mt == Breadcrumb_mt then&lt;br /&gt;
			return Breadcrumb(connect_breadcrumbs(left, right))&lt;br /&gt;
		end&lt;br /&gt;
&lt;br /&gt;
		local root_elem, leaf_elem = breadcrumb_to_built_elem(left)&lt;br /&gt;
		leaf_elem[SYM_CHILDREN][1] = right&lt;br /&gt;
		return root_elem&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
	---@param left BareElement | BuildingElement | any&lt;br /&gt;
	---@param right any | BareElement | BuildingElement&lt;br /&gt;
	---@return Breadcrumb | BuiltElement&lt;br /&gt;
	---@nodiscard&lt;br /&gt;
	local function elem_div(left, right)&lt;br /&gt;
		local left_mt = getmt(left)&lt;br /&gt;
		if left_mt ~= BareElement_mt and left_mt ~= BuildingElement_mt then&lt;br /&gt;
			error(&amp;#039;attempt to div a &amp;#039;..type(left)..&amp;#039; with an element&amp;#039;, 2)&lt;br /&gt;
		end&lt;br /&gt;
		---@diagnostic disable-next-line: assign-type-mismatch&lt;br /&gt;
		local tag_name = left[SYM_TAG_NAME]  ---@type string&lt;br /&gt;
		if void_elems[tag_name] then&lt;br /&gt;
			error(&amp;#039;attempt to perform division on a void element&amp;#039;, 2)&lt;br /&gt;
		end&lt;br /&gt;
		return breadcrumb_div(Breadcrumb({ tag_name }, { rawget(left, SYM_ATTR_MAP) }), right)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	BareElement_mt = node_mts:register {&lt;br /&gt;
		__tostring = SYM_STRING.getter,  --&amp;gt; string&lt;br /&gt;
		---Semantic sugar for setting attributes.&lt;br /&gt;
		---e.g. `local elem = acandy.div[&amp;#039;#id cls1 cls2&amp;#039;]`&lt;br /&gt;
		---@param self BareElement&lt;br /&gt;
		---@param attrs string | table&lt;br /&gt;
		---@return BuildingElement&lt;br /&gt;
		__index = function (self, attrs)&lt;br /&gt;
			local attr_map&lt;br /&gt;
			if type(attrs) == &amp;#039;string&amp;#039; then&lt;br /&gt;
				attr_map = utils.parse_shorthand_attrs(attrs)&lt;br /&gt;
			elseif container_level_of(attrs) == 2 then&lt;br /&gt;
				attr_map = {}&lt;br /&gt;
				for k, v in pairs(attrs) do&lt;br /&gt;
					if type(k) == &amp;#039;string&amp;#039; then&lt;br /&gt;
						if not utils.is_html_attr_name(k) then&lt;br /&gt;
							error(&amp;#039;invalid attribute name: &amp;#039;..k, 2)&lt;br /&gt;
						end&lt;br /&gt;
						attr_map[k] = v&lt;br /&gt;
					end&lt;br /&gt;
				end&lt;br /&gt;
			else&lt;br /&gt;
				error(&amp;#039;invalid attributes: &amp;#039;..tostring(attrs), 2)&lt;br /&gt;
			end&lt;br /&gt;
			---@diagnostic disable-next-line: param-type-mismatch&lt;br /&gt;
			return BuildingElement(self[SYM_TAG_NAME], attr_map)&lt;br /&gt;
		end,&lt;br /&gt;
		__call = new_built_elem_from_props,  --&amp;gt; BuiltElement&lt;br /&gt;
		__div = elem_div,  --&amp;gt; Breadcrumb | BuiltElement&lt;br /&gt;
		__newindex = error_emitters.unbuilt_elem_newindex,&lt;br /&gt;
	}&lt;br /&gt;
	BuildingElement_mt = node_mts:register {&lt;br /&gt;
		__tostring = elem_to_string,  --&amp;gt; string&lt;br /&gt;
		__call = new_built_elem_from_props,  --&amp;gt; BuiltElement&lt;br /&gt;
		__div = elem_div,  --&amp;gt; Breadcrumb | BuiltElement&lt;br /&gt;
		__index = error_emitters.unbuilt_elem_index,&lt;br /&gt;
		__newindex = error_emitters.unbuilt_elem_newindex,&lt;br /&gt;
	}&lt;br /&gt;
	BuiltElement_mt = node_mts:register {&lt;br /&gt;
		__tostring = elem_to_string,  --&amp;gt; string&lt;br /&gt;
		__index = get_elem_prop,&lt;br /&gt;
		__newindex = set_elem_prop,&lt;br /&gt;
		__div = error_emitters.built_elem_div,&lt;br /&gt;
	}&lt;br /&gt;
	Breadcrumb_mt = node_mts:register {&lt;br /&gt;
		__tostring = breadcrumb_to_string,  --&amp;gt; string&lt;br /&gt;
		__call = function (self, props)  --&amp;gt; BuiltElement&lt;br /&gt;
			local root_elem, leaf_elem = breadcrumb_to_built_elem(self)&lt;br /&gt;
			local new_leaf_elem = new_built_elem_from_props(leaf_elem, props)&lt;br /&gt;
			leaf_elem[SYM_ATTR_MAP] = new_leaf_elem[SYM_ATTR_MAP]&lt;br /&gt;
			leaf_elem[SYM_CHILDREN] = new_leaf_elem[SYM_CHILDREN]&lt;br /&gt;
			return root_elem&lt;br /&gt;
		end,&lt;br /&gt;
		__div = function (left, right)  --&amp;gt; Breadcrumb | BuiltElement&lt;br /&gt;
			if getmt(left) ~= Breadcrumb_mt then&lt;br /&gt;
				error(&amp;#039;attempt to div a &amp;#039;..type(left)..&amp;#039; with an breadcrumb&amp;#039;, 2)&lt;br /&gt;
			end&lt;br /&gt;
			return breadcrumb_div(left, right)&lt;br /&gt;
		end,&lt;br /&gt;
		__index = error_emitters.unbuilt_elem_index,&lt;br /&gt;
		__newindex = error_emitters.unbuilt_elem_newindex,&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
	---@class ElementEntry&lt;br /&gt;
	---@field [string] BareElement&lt;br /&gt;
	local a = setmt({}, {&lt;br /&gt;
		---When indexing a uncached tag name, return a constructor of that element.&lt;br /&gt;
		---@param key string&lt;br /&gt;
		---@return BareElement&lt;br /&gt;
		__index = function (self, key)&lt;br /&gt;
			if not utils.is_html_tag_name(key) then&lt;br /&gt;
				error(&amp;#039;invalid tag name: &amp;#039;..tostring(key), 2)&lt;br /&gt;
			end&lt;br /&gt;
&lt;br /&gt;
			local lower_key = key:lower()&lt;br /&gt;
			local bare_elem&lt;br /&gt;
			if lower_key ~= key then&lt;br /&gt;
				bare_elem = rawget(self, lower_key)&lt;br /&gt;
				if not bare_elem then&lt;br /&gt;
					bare_elem = BareElement(lower_key)&lt;br /&gt;
					self[lower_key] = bare_elem&lt;br /&gt;
				end&lt;br /&gt;
			else&lt;br /&gt;
				bare_elem = BareElement(key)&lt;br /&gt;
			end&lt;br /&gt;
			self[key] = bare_elem&lt;br /&gt;
			return bare_elem&lt;br /&gt;
		end,&lt;br /&gt;
	})&lt;br /&gt;
&lt;br /&gt;
	---@class SomeBareElements&lt;br /&gt;
	---@field [string] fun(props: any): BuiltElement[]&lt;br /&gt;
	---@overload fun(props: any): BuiltElement[]&lt;br /&gt;
&lt;br /&gt;
	---@class SomeElementsEntry&lt;br /&gt;
	---@field [string] SomeBareElements&lt;br /&gt;
	local some = setmt({}, {&lt;br /&gt;
		__index = function (_, key)&lt;br /&gt;
			local bare_elem = a[key]&lt;br /&gt;
			local mt = {}&lt;br /&gt;
&lt;br /&gt;
			function mt:__index(shorthand)&lt;br /&gt;
				local building_elem = bare_elem[shorthand]&lt;br /&gt;
				return function (...)&lt;br /&gt;
					---@diagnostic disable-next-line: param-type-mismatch&lt;br /&gt;
					return setmt(utils.map_varargs(building_elem, ...), Fragment_mt)&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
&lt;br /&gt;
			function mt:__call(...)&lt;br /&gt;
				---@diagnostic disable-next-line: param-type-mismatch&lt;br /&gt;
				return setmt(utils.map_varargs(bare_elem, ...), Fragment_mt)&lt;br /&gt;
			end&lt;br /&gt;
&lt;br /&gt;
			return setmt({}, mt)&lt;br /&gt;
		end,&lt;br /&gt;
	})&lt;br /&gt;
&lt;br /&gt;
	---@class ACandy&lt;br /&gt;
	local acandy = {&lt;br /&gt;
		a = a,&lt;br /&gt;
		some = some,&lt;br /&gt;
		Comment = classes.Comment,&lt;br /&gt;
		Doctype = classes.Doctype,&lt;br /&gt;
		Fragment = Fragment,&lt;br /&gt;
		Raw = classes.Raw,&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	return acandy&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local ACANDY_EXPORTED_NAMES = {&lt;br /&gt;
	a = true,&lt;br /&gt;
	some = true,&lt;br /&gt;
	-- classes&lt;br /&gt;
	Comment = true,&lt;br /&gt;
	Doctype = true,&lt;br /&gt;
	Fragment = true,&lt;br /&gt;
	Raw = true,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
---@class ACandyModule: ACandy&lt;br /&gt;
local acandy_module = setmt({&lt;br /&gt;
	ACandy = ACandy,&lt;br /&gt;
}, {&lt;br /&gt;
	__index = function (self, k)&lt;br /&gt;
		if not ACANDY_EXPORTED_NAMES[k] then&lt;br /&gt;
			return nil&lt;br /&gt;
		end&lt;br /&gt;
		local default_acandy = ACandy(&amp;#039;html&amp;#039;)&lt;br /&gt;
		utils.copy_pairs(default_acandy, self)&lt;br /&gt;
		assert(&lt;br /&gt;
			default_acandy[k] ~= nil,&lt;br /&gt;
			(&amp;#039;`acandy[%q]` should exist but not found, please contact the author&amp;#039;):format(k)&lt;br /&gt;
		)&lt;br /&gt;
		return default_acandy[k]&lt;br /&gt;
	end,&lt;br /&gt;
})&lt;br /&gt;
return acandy_module&lt;/div&gt;</summary>
		<author><name>天明</name></author>
	</entry>
</feed>