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 /xmake/core/base/xml.lua | |
| parent | 29fbfdef99861931d81578eac3434f452efddb8e (diff) | |
improve xml module
Diffstat (limited to 'xmake/core/base/xml.lua')
| -rw-r--r-- | xmake/core/base/xml.lua | 91 |
1 files changed, 79 insertions, 12 deletions
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, "") |
