diff options
| author | ruki <[email protected]> | 2025-11-15 00:54:20 +0800 |
|---|---|---|
| committer | ruki <[email protected]> | 2025-11-15 00:54:20 +0800 |
| commit | d6b3bc59cad34b6279f3df8a2066505ca82a2733 (patch) | |
| tree | fc5b93ceb41573e14c96c54cc99e1d9f9ec7c11f | |
| parent | 29fbfdef99861931d81578eac3434f452efddb8e (diff) | |
improve xml module
| -rw-r--r-- | tests/modules/xml/test.lua | 30 | ||||
| -rw-r--r-- | xmake/core/base/xml.lua | 91 | ||||
| -rw-r--r-- | xmake/core/sandbox/modules/import/core/base/xml.lua | 4 |
3 files changed, 110 insertions, 15 deletions
diff --git a/tests/modules/xml/test.lua b/tests/modules/xml/test.lua index eb01aad86..80ce486f6 100644 --- a/tests/modules/xml/test.lua +++ b/tests/modules/xml/test.lua @@ -7,13 +7,18 @@ function test_parse_basic(t) t:are_equal(#doc.children, 2) t:are_equal(doc.children[1].name, "item") t:are_equal(xml.text_of(doc.children[1]), "foo") + t:are_equal(doc.children[1].attrs, nil) t:are_equal(doc.children[2].attrs.id, "2") end function test_encode(t) - local doc = xml.new("root", {id = "1"}, { - xml.new("item", {}, {xml.text("foo")}), - xml.new("item", {id = "2"}) + local doc = xml.new({ + name = "root", + attrs = {id = "1"}, + children = { + xml.new({name = "item", children = {xml.text("foo")}}), + xml.new({name = "item", attrs = {id = "2"}}) + } }) local compact = xml.encode(doc) t:are_equal(compact, '<root id="1"><item>foo</item><item id="2"/></root>') @@ -27,3 +32,22 @@ function test_encode(t) t:are_equal(pretty, expected) end +function test_special_nodes(t) + t:are_equal(xml.encode(xml.comment("note")), "<!--note-->") + t:are_equal(xml.encode(xml.cdata("a < b")), "<![CDATA[a < b]]>") + t:are_equal(xml.encode(xml.doctype("note SYSTEM \"note.dtd\"")), "<!DOCTYPE note SYSTEM \"note.dtd\">") + t:are_equal(xml.encode(xml.empty("br")), "<br/>") +end + +function test_parse_special_nodes(t) + local doc = xml.decode([[<root><!--note--><![CDATA[a < b]]><child/></root>]]) + t:are_equal(doc.children[1].name, "__comment__") + t:are_equal(doc.children[1].children[1], "note") + t:are_equal(doc.children[2].name, "__cdata__") + t:are_equal(doc.children[2].children[1], "a < b") + t:are_equal(doc.children[3].name, "child") + local nodes = xml.decode([[<!DOCTYPE note SYSTEM "note.dtd"><root/>]]) + t:are_equal(nodes[1].name, "__doctype__") + t:are_equal(nodes[2].name, "root") +end + diff --git a/xmake/core/base/xml.lua b/xmake/core/base/xml.lua index 7ac12f133..a3f2c96f0 100644 --- a/xmake/core/base/xml.lua +++ b/xmake/core/base/xml.lua @@ -45,25 +45,55 @@ function xml._encode_attr(str) end function xml._parse_attrs(attrstr) - local attrs = {} + local attrs attrstr:gsub("([%w_:%-%.]+)%s*=%s*([\"'])(.-)%2", function(key, quote, value) + attrs = attrs or {} attrs[key] = xml._decode_entities(value) end) return attrs end -- create an xml element node -function xml.new(name, attrs, children) +-- e.g. `local node = xml.new({name = "item", attrs = {id = "1"}, children = {xml.text("value")}})` +function xml.new(opt) + opt = opt or {} return { - name = name, - attrs = attrs or {}, - children = children or {} + name = opt.name, + attrs = opt.attrs, + kind = opt.kind or "element", + text = opt.text, + children = opt.children or {} } end -- create a text node +-- e.g. `local textnode = xml.text("hello")` function xml.text(value) - return {type = "text", text = value or ""} + return xml.new({kind = "text", text = value or ""}) +end + +-- create an empty element node +-- e.g. `local br = xml.empty("br", {class = "line"})` +function xml.empty(name, attrs) + return xml.new({name = name, attrs = attrs}) +end + +-- create a comment node +-- e.g. `local comment = xml.comment("generated by xmake")` +function xml.comment(value) + return xml.new({kind = "comment", text = value or ""}) +end + +-- create a CDATA node +-- e.g. `local cdata = xml.cdata("if (a < b) { ... }")` +function xml.cdata(value) + return xml.new({kind = "cdata", text = value or ""}) +end + +-- create a doctype node +-- e.g. `local doc = xml.doctype('html')` +function xml.doctype(value) + return xml.new({kind = "doctype", text = value or ""}) end function xml._append_text(stack, text, opt) @@ -74,7 +104,7 @@ function xml._append_text(stack, text, opt) if text ~= "" then local top = stack[#stack] top.children = top.children or {} - table.insert(top.children, {type = "text", text = xml._decode_entities(text)}) + table.insert(top.children, xml.text(xml._decode_entities(text))) end end @@ -88,6 +118,7 @@ function xml._handle_closing(stack, tagname) end -- decode xml string to tree node(s) +-- e.g. `local doc, err = xml.decode("<root><item>foo</item></root>")` function xml.decode(data, opt) opt = opt or {} local root = {name = "__root__", attrs = {}, children = {}} @@ -110,7 +141,31 @@ function xml.decode(data, opt) if not close then return nil, "unterminated xml comment" end + local value = data:sub(lt + 4, close - 1) + local top = stack[#stack] + top.children = top.children or {} + table.insert(top.children, xml.comment(value)) + i = close + 3 + elseif data:sub(lt + 1, lt + 8) == "![CDATA[" then + local close = data:find("]]>", lt + 9, true) + if not close then + return nil, "unterminated cdata section" + end + local value = data:sub(lt + 9, close - 1) + local top = stack[#stack] + top.children = top.children or {} + table.insert(top.children, xml.cdata(value)) i = close + 3 + elseif data:sub(lt + 1, lt + 9):upper() == "!DOCTYPE" then + local close = data:find(">", lt + 9) + if not close then + return nil, "unterminated doctype declaration" + end + local value = data:sub(lt + 10, close - 1) + local top = stack[#stack] + top.children = top.children or {} + table.insert(top.children, xml.doctype(value)) + i = close + 1 elseif data:sub(lt + 1, lt + 1) == "?" then local close = data:find("?>", lt + 2, true) if not close then @@ -146,7 +201,7 @@ function xml.decode(data, opt) end local tagname, attrstr = inside:match("^%s*([^%s>]+)%s*(.-)%s*$") local attrs = xml._parse_attrs(attrstr or "") - local node = {name = tagname, attrs = attrs, children = {}} + local node = xml.empty(tagname, attrs) local top = stack[#stack] top.children = top.children or {} table.insert(top.children, node) @@ -180,13 +235,19 @@ end function xml._encode_node(node, opt, level) opt = opt or {} level = level or 0 - if node.type == "text" then + if node.kind == "text" then local indent = xml._indent(opt, level) local text = xml._encode_text(tostring(node.text or "")) if opt.pretty then return indent .. text end return text + elseif node.kind == "comment" then + return xml._indent(opt, level) .. string.format("<!--%s-->", tostring(node.text or "")) + elseif node.kind == "cdata" then + return xml._indent(opt, level) .. string.format("<![CDATA[%s]]>", tostring(node.text or "")) + elseif node.kind == "doctype" then + return xml._indent(opt, level) .. string.format("<!DOCTYPE %s>", tostring(node.text or "")) end local attrs = {} for k, v in pairs(node.attrs or {}) do @@ -201,7 +262,7 @@ function xml._encode_node(node, opt, level) return xml._indent(opt, level) .. open .. "/>" end local newline = opt.pretty and "\n" or "" - if #node.children == 1 and node.children[1].type == "text" then + if #node.children == 1 and node.children[1].kind == "text" then local text = xml._encode_text(tostring(node.children[1].text or "")) return string.format("%s%s>%s</%s>", xml._indent(opt, level), open, text, node.name) end @@ -215,12 +276,14 @@ function xml._encode_node(node, opt, level) end -- encode xml node to string +-- e.g. `local xmlstr = xml.encode(node, {pretty = true, indent = 2})` function xml.encode(node, opt) opt = opt or {} return xml._encode_node(node, opt, 0) end -- load xml file +-- e.g. `local doc, err = xml.load("foo.xml")` function xml.load(filepath, opt) local data, err = io.readfile(filepath, opt) if not data then @@ -230,6 +293,7 @@ function xml.load(filepath, opt) end -- save xml node to file +-- e.g. `assert(xml.save("foo.xml", node, {pretty = true}))` function xml.save(filepath, node, opt) local data = xml.encode(node, opt) if not data then @@ -239,6 +303,8 @@ function xml.save(filepath, node, opt) end -- find the first child node with the given name +-- e.g. `local doc = xml.decode("<root><item id='1'/></root>")` +-- `local item = xml.find(doc, "item")` function xml.find(node, name) if not node or not node.children then return nil @@ -251,14 +317,15 @@ function xml.find(node, name) end -- get concatenated text from child nodes +-- e.g. `local text = xml.text_of(xml.decode("<item>foo</item>"))` function xml.text_of(node) if not node or not node.children then return "" end local buffer = {} for _, child in ipairs(node.children) do - if child.type == "text" then - table.insert(buffer, child.text) + if child.kind == "text" then + table.insert(buffer, child.text or "") end end return table.concat(buffer, "") diff --git a/xmake/core/sandbox/modules/import/core/base/xml.lua b/xmake/core/sandbox/modules/import/core/base/xml.lua index 7867c3295..6af763e47 100644 --- a/xmake/core/sandbox/modules/import/core/base/xml.lua +++ b/xmake/core/sandbox/modules/import/core/base/xml.lua @@ -31,6 +31,10 @@ sandbox_core_base_xml.find = xml.find sandbox_core_base_xml.text_of = xml.text_of sandbox_core_base_xml.text = xml.text sandbox_core_base_xml.new = xml.new +sandbox_core_base_xml.empty = xml.empty +sandbox_core_base_xml.comment = xml.comment +sandbox_core_base_xml.cdata = xml.cdata +sandbox_core_base_xml.doctype = xml.doctype -- decode xml data function sandbox_core_base_xml.decode(data, opt) |
