Files
Basalt2/docs/references/plugins/xml.md
2025-02-16 14:12:49 +00:00

5.4 KiB

local errorManager = require("errorManager")

local function parseTag(str) local tag = { attributes = {} } tag.name = str:match("<(%w+)") for k,v in str:gmatch('%s(%w+)="([^"]-)"') do tag.attributes[k] = v end return tag end

local function parseXML(self, xmlString) local stack = {} local root = {children = {}} local current = root local inCDATA = false local cdataContent = ""

for line in xmlString:gmatch("[^\r\n]+") do
    line = line:match("^%s*(.-)%s*$")
    self.basalt.LOGGER.debug("Parsing line: " .. line)

    if line:match("^<!%[CDATA%[") then
        inCDATA = true
        cdataContent = ""
    elseif line:match("%]%]>$") and inCDATA then
        inCDATA = false
        current.content = cdataContent
    elseif inCDATA then
        cdataContent = cdataContent .. line .. "\n"
    elseif line:match("^<[^/]") then
        local tag = parseTag(line)
        tag.children = {}
        tag.content = ""
        table.insert(current.children, tag)

        if not line:match("/>$") then
            table.insert(stack, current)
            current = tag
        end
    elseif line:match("^</") then
        current = table.remove(stack)
    end
end
return root

end

local function evaluateExpression(expr, scope) if not expr:match("^%${.*}$") then return expr:gsub("%${(.-)}", function(e) local env = setmetatable({}, {__index = function(_, k) return scope and scope[k] or _ENV[k] end})

        local func, err = load("return " .. e, "expression", "t", env)
        if not func then
            errorManager.error("Failed to parse expression: " .. err)
        end
        return tostring(func())
    end)
end

expr = expr:match("^%${(.*)}$")
local env = setmetatable({}, {__index = function(_, k)
    return scope and scope[k] or _ENV[k]
end})

local func, err = load("return " .. expr, "expression", "t", env)
if not func then
    errorManager.error("Failed to parse expression: " .. err)
end
return func()

end

local function convertValue(value, propertyType, scope) if propertyType == "string" and type(value) == "string" then if value:find("${") then return evaluateExpression(value, scope) end end

if type(value) == "string" and value:match("^%${.*}$") then
    return evaluateExpression(value, scope)
end

if propertyType == "number" then
    return tonumber(value)
elseif propertyType == "boolean" then
    return value == "true"
elseif propertyType == "color" then
    return colors[value]
elseif propertyType == "table" then
    local env = setmetatable({}, { __index = _ENV })
    local func = load("return "..value, nil, "t", env)
    if func then
        return func()
    end
end
return value

end

local function handleEvent(node, element, scope) for attr, value in pairs(node.attributes) do if attr:match("^on%u") then local eventName = attr:sub(3,3):lower() .. attr:sub(4) if scope[value] then element["on"..eventName:sub(1,1):upper()..eventName:sub(2)](element, scope[value]) end end end

for _, child in ipairs(node.children or {}) do
    if child.name and child.name:match("^on%u") then
        local eventName = child.name:sub(3,3):lower() .. child.name:sub(4)

        if child.content then
            local code = child.content:gsub("^%s+", ""):gsub("%s+$", "")

            local func, err = load(string.format([[
                return %s
            ]], code), "event", "t", scope)

            if err then
                errorManager.error("Failed to parse event: " .. err)
            elseif func then
                element["on"..eventName:sub(1,1):upper()..eventName:sub(2)](element, func())
            end
        end
    end
end

end

local BaseElement = {}

function BaseElement:fromXML(node) for attr, value in pairs(node.attributes) do local config = self:getPropertyConfig(attr) if config then local convertedValue = convertValue(value, config.type) self.set(attr, convertedValue) end end return self end

local Container = {}

function Container:loadXML(content, scope) local tree = parseXML(self, content)

local function createElements(nodes, parent, scope)
    for _, node in ipairs(nodes.children) do
        if not node.name:match("^on") then
            local elementType = node.name:sub(1,1):upper() .. node.name:sub(2)
            local element = parent["add"..elementType](parent, node.attributes.name)

            for attr, value in pairs(node.attributes) do
                local config = element:getPropertyConfig(attr)
                if config then
                    local convertedValue = convertValue(value, config.type, scope)
                    element.set(attr, convertedValue)
                end
            end

            handleEvent(node, element, scope)

            if #node.children > 0 then
                createElements(node, element, scope)
            end
        end
    end
end

createElements(tree, self, scope)
return self

end

return { BaseElement = BaseElement, Container = Container }