Reworked XML #2
This commit is contained in:
@@ -62,7 +62,7 @@ Container.defineProperty(Container, "offsetY", {default = 0, type = "number", ca
|
|||||||
return value
|
return value
|
||||||
end})
|
end})
|
||||||
|
|
||||||
---@combinedProperty offset {offsetX offsetY} Combined property for offsetX and offsetY
|
---@combinedProperty offset {offsetX number, offsetY number} Combined property for offsetX and offsetY
|
||||||
Container.combineProperties(Container, "offset", "offsetX", "offsetY")
|
Container.combineProperties(Container, "offset", "offsetX", "offsetY")
|
||||||
|
|
||||||
for k, _ in pairs(elementManager:getElementList()) do
|
for k, _ in pairs(elementManager:getElementList()) do
|
||||||
|
|||||||
@@ -3,13 +3,12 @@ local errorManager = require("errorManager")
|
|||||||
local propertySystem = require("propertySystem")
|
local propertySystem = require("propertySystem")
|
||||||
local expect = require("libraries/expect")
|
local expect = require("libraries/expect")
|
||||||
|
|
||||||
|
|
||||||
--- This is the UI Manager and the starting point for your project. The following functions allow you to influence the default behavior of Basalt.
|
--- This is the UI Manager and the starting point for your project. The following functions allow you to influence the default behavior of Basalt.
|
||||||
---
|
---
|
||||||
--- Before you can access Basalt, you need to add the following code on top of your file:
|
--- Before you can access Basalt, you need to add the following code on top of your file:
|
||||||
--- @usage local basalt = require("basalt")
|
--- @usage local basalt = require("basalt")
|
||||||
--- What this code does is it loads basalt into the project, and you can access it by using the variable defined as "basalt".
|
--- What this code does is it loads basalt into the project, and you can access it by using the variable defined as "basalt".
|
||||||
--- @class Basalt
|
--- @class basalt
|
||||||
--- @field traceback boolean Whether to show a traceback on errors
|
--- @field traceback boolean Whether to show a traceback on errors
|
||||||
--- @field _events table A table of events and their callbacks
|
--- @field _events table A table of events and their callbacks
|
||||||
--- @field _schedule function[] A table of scheduled functions
|
--- @field _schedule function[] A table of scheduled functions
|
||||||
@@ -92,7 +91,7 @@ end
|
|||||||
|
|
||||||
--- Creates and returns a new BaseFrame
|
--- Creates and returns a new BaseFrame
|
||||||
--- @shortDescription Creates a new BaseFrame
|
--- @shortDescription Creates a new BaseFrame
|
||||||
--- @return table BaseFrame The created frame instance
|
--- @return BaseFrame BaseFrame The created frame instance
|
||||||
--- @usage local mainFrame = basalt.createFrame()
|
--- @usage local mainFrame = basalt.createFrame()
|
||||||
function basalt.createFrame()
|
function basalt.createFrame()
|
||||||
local frame = basalt.create("BaseFrame")
|
local frame = basalt.create("BaseFrame")
|
||||||
@@ -111,7 +110,7 @@ end
|
|||||||
|
|
||||||
--- Gets or creates the main frame
|
--- Gets or creates the main frame
|
||||||
--- @shortDescription Gets or creates the main frame
|
--- @shortDescription Gets or creates the main frame
|
||||||
--- @return BaseFrame table The main frame instance
|
--- @return BaseFrame BaseFrame The main frame instance
|
||||||
--- @usage local frame = basalt.getMainFrame()
|
--- @usage local frame = basalt.getMainFrame()
|
||||||
function basalt.getMainFrame()
|
function basalt.getMainFrame()
|
||||||
if(mainFrame == nil)then
|
if(mainFrame == nil)then
|
||||||
|
|||||||
@@ -196,8 +196,7 @@ end
|
|||||||
|
|
||||||
---@splitClass
|
---@splitClass
|
||||||
|
|
||||||
--- Container benchmarking methods
|
---@class Container : VisualElement
|
||||||
---@class Container
|
|
||||||
local Container = {}
|
local Container = {}
|
||||||
|
|
||||||
--- Enables benchmarking for a container and all its children
|
--- Enables benchmarking for a container and all its children
|
||||||
|
|||||||
@@ -1,527 +1,235 @@
|
|||||||
local errorManager = require("errorManager")
|
local errorManager = require("errorManager")
|
||||||
|
local log = require("log")
|
||||||
|
local XMLNode = {
|
||||||
|
new = function(tag)
|
||||||
|
return {
|
||||||
|
tag = tag,
|
||||||
|
value = nil,
|
||||||
|
attributes = {},
|
||||||
|
children = {},
|
||||||
|
|
||||||
local XMLParser = {}
|
addChild = function(self, child)
|
||||||
local TokenType = {
|
table.insert(self.children, child)
|
||||||
TAG_OPEN = "TAG_OPEN",
|
end,
|
||||||
TAG_CLOSE = "TAG_CLOSE",
|
|
||||||
TAG_SELF_CLOSE = "TAG_SELF_CLOSE",
|
addAttribute = function(self, tag, value)
|
||||||
ATTRIBUTE = "ATTRIBUTE",
|
self.attributes[tag] = value
|
||||||
TEXT = "TEXT",
|
end
|
||||||
CDATA = "CDATA",
|
}
|
||||||
COMMENT = "COMMENT"
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
local function tokenize(xml)
|
local parseAttributes = function(node, s)
|
||||||
local tokens = {}
|
local _, _ = string.gsub(s, "(%w+)=([\"'])(.-)%2", function(attribute, _, value)
|
||||||
local position = 1
|
node:addAttribute(attribute, "\"" .. value .. "\"")
|
||||||
local lineNumber = 1
|
end)
|
||||||
|
local _, _ = string.gsub(s, "(%w+)={(.-)}", function(attribute, expression)
|
||||||
while position <= #xml do
|
node:addAttribute(attribute, expression)
|
||||||
local char = xml:sub(position, position)
|
|
||||||
|
|
||||||
if char:match("%s") then
|
|
||||||
position = position + 1
|
|
||||||
elseif xml:sub(position, position + 8) == "<![CDATA[" then
|
|
||||||
local endPos = xml:find("]]>", position + 9)
|
|
||||||
if not endPos then errorManager.error("Unclosed CDATA section") end
|
|
||||||
table.insert(tokens, {
|
|
||||||
type = TokenType.CDATA,
|
|
||||||
value = xml:sub(position + 9, endPos - 1)
|
|
||||||
})
|
|
||||||
position = endPos + 3
|
|
||||||
elseif xml:sub(position, position + 3) == "<!--" then
|
|
||||||
local endPos = xml:find("-->", position + 4)
|
|
||||||
if not endPos then errorManager.error("Unclosed comment") end
|
|
||||||
table.insert(tokens, {
|
|
||||||
type = TokenType.COMMENT,
|
|
||||||
value = xml:sub(position + 4, endPos - 1)
|
|
||||||
})
|
|
||||||
position = endPos + 3
|
|
||||||
elseif char == "<" then
|
|
||||||
if xml:sub(position + 1, position + 1) == "/" then
|
|
||||||
local endPos = xml:find(">", position)
|
|
||||||
if not endPos then errorManager.error("Unclosed tag") end
|
|
||||||
table.insert(tokens, {
|
|
||||||
type = TokenType.TAG_CLOSE,
|
|
||||||
value = xml:sub(position + 2, endPos - 1):match("^%s*(.-)%s*$")
|
|
||||||
})
|
|
||||||
position = endPos + 1
|
|
||||||
else
|
|
||||||
local tagContent = ""
|
|
||||||
position = position + 1
|
|
||||||
local selfClosing = false
|
|
||||||
|
|
||||||
while position <= #xml do
|
|
||||||
char = xml:sub(position, position)
|
|
||||||
if char == ">" then
|
|
||||||
table.insert(tokens, {
|
|
||||||
type = selfClosing and TokenType.TAG_SELF_CLOSE or TokenType.TAG_OPEN,
|
|
||||||
value = tagContent:match("^%s*(.-)%s*$")
|
|
||||||
})
|
|
||||||
position = position + 1
|
|
||||||
break
|
|
||||||
elseif char == "/" and xml:sub(position + 1, position + 1) == ">" then
|
|
||||||
|
|
||||||
table.insert(tokens, {
|
|
||||||
type = TokenType.TAG_SELF_CLOSE,
|
|
||||||
value = tagContent:match("^%s*(.-)%s*$")
|
|
||||||
})
|
|
||||||
position = position + 2
|
|
||||||
break
|
|
||||||
elseif char == "/" and xml:sub(position - 1, position - 1):match("%s") then
|
|
||||||
|
|
||||||
selfClosing = true
|
|
||||||
else
|
|
||||||
tagContent = tagContent .. char
|
|
||||||
end
|
|
||||||
position = position + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
local text = ""
|
|
||||||
while position <= #xml and xml:sub(position, position) ~= "<" do
|
|
||||||
text = text .. xml:sub(position, position)
|
|
||||||
position = position + 1
|
|
||||||
end
|
|
||||||
if text:match("%S") then
|
|
||||||
table.insert(tokens, {
|
|
||||||
type = TokenType.TEXT,
|
|
||||||
value = text:match("^%s*(.-)%s*$")
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if char == "\n" then
|
|
||||||
lineNumber = lineNumber + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return tokens
|
|
||||||
end
|
|
||||||
|
|
||||||
local function parse(tokens)
|
|
||||||
local root = {
|
|
||||||
name = "root",
|
|
||||||
children = {},
|
|
||||||
attributes = {}
|
|
||||||
}
|
|
||||||
local stack = {root}
|
|
||||||
local current = root
|
|
||||||
|
|
||||||
local i = 1
|
|
||||||
while i <= #tokens do
|
|
||||||
local token = tokens[i]
|
|
||||||
|
|
||||||
if token.type == TokenType.TAG_OPEN then
|
|
||||||
local tagName, attributes = token.value:match("(%S+)(.*)")
|
|
||||||
local node = {
|
|
||||||
name = tagName,
|
|
||||||
attributes = {},
|
|
||||||
children = {},
|
|
||||||
parent = current,
|
|
||||||
line = token.line
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, value in attributes:gmatch('%s(%w+)="([^"]-)"') do
|
|
||||||
node.attributes[key] = value
|
|
||||||
end
|
|
||||||
|
|
||||||
table.insert(current.children, node)
|
|
||||||
table.insert(stack, node)
|
|
||||||
current = node
|
|
||||||
|
|
||||||
elseif token.type == TokenType.TAG_SELF_CLOSE then
|
|
||||||
|
|
||||||
local tagName, attributes = token.value:match("(%S+)(.*)")
|
|
||||||
local node = {
|
|
||||||
name = tagName,
|
|
||||||
attributes = {},
|
|
||||||
children = {},
|
|
||||||
parent = current,
|
|
||||||
line = token.line
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, value in attributes:gmatch('%s(%w+)="([^"]-)"') do
|
|
||||||
node.attributes[key] = value
|
|
||||||
end
|
|
||||||
|
|
||||||
table.insert(current.children, node)
|
|
||||||
|
|
||||||
elseif token.type == TokenType.TAG_CLOSE then
|
|
||||||
if current.name ~= token.value then
|
|
||||||
errorManager.error(string.format("Mismatched closing tag: expected </%s>, got </%s>",
|
|
||||||
current.name, token.value))
|
|
||||||
end
|
|
||||||
table.remove(stack)
|
|
||||||
current = stack[#stack]
|
|
||||||
|
|
||||||
elseif token.type == TokenType.TEXT then
|
|
||||||
table.insert(current.children, {
|
|
||||||
name = "#text",
|
|
||||||
value = token.value,
|
|
||||||
line = token.line
|
|
||||||
})
|
|
||||||
|
|
||||||
elseif token.type == TokenType.CDATA then
|
|
||||||
table.insert(current.children, {
|
|
||||||
name = "#cdata",
|
|
||||||
value = token.value,
|
|
||||||
line = token.line
|
|
||||||
})
|
|
||||||
|
|
||||||
elseif token.type == TokenType.COMMENT then
|
|
||||||
table.insert(current.children, {
|
|
||||||
name = "#comment",
|
|
||||||
value = token.value,
|
|
||||||
line = token.line
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
i = i + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
return root
|
|
||||||
end
|
|
||||||
|
|
||||||
function XMLParser.parse(xmlString)
|
|
||||||
local tokens = tokenize(xmlString)
|
|
||||||
return parse(tokens)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function evaluateExpression(expr, scope)
|
|
||||||
if not expr then return expr end
|
|
||||||
|
|
||||||
if expr:match("^%${.*}$") then
|
|
||||||
local inner = expr:match("^%${(.*)}$")
|
|
||||||
if inner:match("^[%w_]+$") then
|
|
||||||
if scope and scope[inner] then
|
|
||||||
return scope[inner]
|
|
||||||
else
|
|
||||||
errorManager.error(string.format('Variable "%s" not found in scope', inner))
|
|
||||||
return expr
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local env = setmetatable({}, {
|
|
||||||
__index = function(_, k)
|
|
||||||
if scope and scope[k] then
|
|
||||||
return scope[k]
|
|
||||||
elseif _ENV[k] then
|
|
||||||
return _ENV[k]
|
|
||||||
else
|
|
||||||
error(string.format('Variable "%s" not found in scope', k))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
})
|
|
||||||
|
|
||||||
local func, err = load("return " .. inner, "expression", "t", env)
|
|
||||||
if not func then
|
|
||||||
errorManager.error("Failed to parse expression: " .. err)
|
|
||||||
return expr
|
|
||||||
end
|
|
||||||
|
|
||||||
local ok, result = pcall(func)
|
|
||||||
if not ok then
|
|
||||||
errorManager.error("Failed to evaluate expression: " .. result)
|
|
||||||
return expr
|
|
||||||
end
|
|
||||||
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
|
|
||||||
return expr:gsub("%${([^}]+)}", function(e)
|
|
||||||
if e:match("^[%w_]+$") then
|
|
||||||
if scope and scope[e] then
|
|
||||||
return tostring(scope[e])
|
|
||||||
else
|
|
||||||
errorManager.error(string.format('Variable "%s" not found in scope', e))
|
|
||||||
return e
|
|
||||||
end
|
|
||||||
end
|
|
||||||
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)
|
|
||||||
return e
|
|
||||||
end
|
|
||||||
return tostring(func())
|
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function convertValue(value, propertyType, scope)
|
local XMLParser = {
|
||||||
if propertyType == "string" and type(value) == "string" then
|
parseText = function(xmlText)
|
||||||
if value:find("${") then
|
local stack = {}
|
||||||
return evaluateExpression(value, scope)
|
local top = XMLNode.new()
|
||||||
end
|
table.insert(stack, top)
|
||||||
end
|
local ni, c, label, xarg, empty
|
||||||
|
local i, j = 1, 1
|
||||||
if type(value) == "string" and value:match("^%${.*}$") then
|
while true do
|
||||||
return evaluateExpression(value, scope)
|
ni, j, c, label, xarg, empty = string.find(xmlText, "<(%/?)([%w_:]+)(.-)(%/?)>", i)
|
||||||
end
|
if not ni then break end
|
||||||
|
local text = string.sub(xmlText, i, ni - 1);
|
||||||
if propertyType == "number" then
|
if not string.find(text, "^%s*$") then
|
||||||
if(tonumber(value) == nil) then
|
local lVal = (top.value or "") .. text
|
||||||
return value
|
stack[#stack].value = lVal
|
||||||
end
|
end
|
||||||
return tonumber(value)
|
if empty == "/" then
|
||||||
elseif propertyType == "boolean" then
|
local lNode = XMLNode.new(label)
|
||||||
return value == "true"
|
parseAttributes(lNode, xarg)
|
||||||
elseif propertyType == "color" then
|
top:addChild(lNode)
|
||||||
return colors[value]
|
elseif c == "" then
|
||||||
elseif propertyType == "table" then
|
local lNode = XMLNode.new(label)
|
||||||
local env = setmetatable({}, { __index = _ENV })
|
parseAttributes(lNode, xarg)
|
||||||
local func = load("return "..value, nil, "t", env)
|
table.insert(stack, lNode)
|
||||||
if func then
|
top = lNode
|
||||||
return func()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return value
|
|
||||||
end
|
|
||||||
|
|
||||||
local actionHandlers = {
|
|
||||||
setProperty = function(node, element, scope)
|
|
||||||
return function(...)
|
|
||||||
local target = node.attributes.target or "self"
|
|
||||||
local targetElement
|
|
||||||
|
|
||||||
if target == "self" then
|
|
||||||
targetElement = element
|
|
||||||
elseif target == "parent" then
|
|
||||||
targetElement = element.parent
|
|
||||||
else
|
else
|
||||||
targetElement = element:getBaseFrame():getChild(target)
|
local toclose = table.remove(stack)
|
||||||
end
|
|
||||||
|
|
||||||
if not targetElement then
|
top = stack[#stack]
|
||||||
errorManager.error(string.format('Target "%s" not found', target))
|
if #stack < 1 then
|
||||||
return
|
errorManager.error("XMLParser: nothing to close with " .. label)
|
||||||
|
end
|
||||||
|
if toclose.tag ~= label then
|
||||||
|
errorManager.error("XMLParser: trying to close " .. toclose.tag .. " with " .. label)
|
||||||
|
end
|
||||||
|
top:addChild(toclose)
|
||||||
end
|
end
|
||||||
|
i = j + 1
|
||||||
local property = node.attributes.property
|
|
||||||
local propertyConfig = targetElement:getPropertyConfig(property)
|
|
||||||
if not propertyConfig then
|
|
||||||
errorManager.error(string.format('Unknown property "%s"', property))
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local value = convertValue(node.attributes.value, propertyConfig.type, scope)
|
|
||||||
targetElement.set(property, value)
|
|
||||||
end
|
end
|
||||||
end,
|
local text = string.sub(xmlText, i);
|
||||||
|
if #stack > 1 then
|
||||||
execute = function(node, element, scope)
|
error("XMLParser: unclosed " .. stack[#stack].tag)
|
||||||
return function(...)
|
|
||||||
local funcName = node.attributes["function"]
|
|
||||||
if not scope[funcName] then
|
|
||||||
errorManager.error(string.format('Function "%s" not found in scope', funcName))
|
|
||||||
return
|
|
||||||
end
|
|
||||||
scope[funcName](element, ...)
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
setValue = function(node, element, scope)
|
|
||||||
return function(...)
|
|
||||||
local name = node.attributes.name
|
|
||||||
local value = convertValue(node.attributes.value, "string", scope)
|
|
||||||
scope[name] = value
|
|
||||||
end
|
end
|
||||||
|
return top.children
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
local eventParameters = {
|
local log = require("log").debug
|
||||||
onClick = {"self", "button", "x", "y"},
|
|
||||||
onScroll = {"self", "direction", "x", "y"},
|
|
||||||
onDrag = {"self", "button", "x", "y"},
|
|
||||||
onKey = {"self", "key"},
|
|
||||||
onChar = {"self", "char"},
|
|
||||||
onKeyUp = {"self", "key"},
|
|
||||||
|
|
||||||
}
|
local function convertValue(value, scope)
|
||||||
|
if value:sub(1,1) == "\"" and value:sub(-1) == "\"" then
|
||||||
local function handleEvent(node, element, scope)
|
value = value:sub(2, -2)
|
||||||
local isEventNode = node.name:match("^on%u")
|
|
||||||
if not isEventNode then return end
|
|
||||||
|
|
||||||
local eventName = node.name:sub(3,3):lower() .. node.name:sub(4)
|
|
||||||
local handlers = {}
|
|
||||||
|
|
||||||
for _, child in ipairs(node.children or {}) do
|
|
||||||
if child.name == "#cdata" then
|
|
||||||
if not isEventNode then
|
|
||||||
errorManager.error("CDATA blocks can only be used inside event tags")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local eventName = node.name:sub(3)
|
|
||||||
local params = eventParameters["on"..eventName] or {"self"}
|
|
||||||
|
|
||||||
local paramString = table.concat(params, ", ")
|
|
||||||
|
|
||||||
local codeTemplate = [[
|
|
||||||
return function(%s)
|
|
||||||
%s
|
|
||||||
end
|
|
||||||
]]
|
|
||||||
|
|
||||||
local env = {}
|
|
||||||
if scope then
|
|
||||||
for k,v in pairs(scope) do
|
|
||||||
env[k] = v
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
env.colors = colors
|
|
||||||
env.term = term
|
|
||||||
env.math = math
|
|
||||||
|
|
||||||
local code = child.value:gsub("^%s+", ""):gsub("%s+$", "")
|
|
||||||
local finalCode = string.format(codeTemplate, paramString, code)
|
|
||||||
|
|
||||||
local func, err = load(finalCode, "event", "t", env)
|
|
||||||
if err then
|
|
||||||
errorManager.error("Failed to parse event: " .. err)
|
|
||||||
elseif func then
|
|
||||||
local eventName = node.name:sub(3,3):lower() .. node.name:sub(4)
|
|
||||||
element["on"..eventName:sub(1,1):upper()..eventName:sub(2)](element, func())
|
|
||||||
end
|
|
||||||
elseif child.name ~= "#text" then
|
|
||||||
local handler = actionHandlers[child.name]
|
|
||||||
if not handler then
|
|
||||||
errorManager.error(string.format('Unknown action tag "%s"', child.name))
|
|
||||||
return
|
|
||||||
end
|
|
||||||
table.insert(handlers, handler(child, element, scope))
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if #handlers > 0 then
|
if value:sub(1,2) == "${" and value:sub(-1) == "}" then
|
||||||
element["on"..eventName:sub(1,1):upper()..eventName:sub(2)](element, function(...)
|
value = value:sub(3, -2)
|
||||||
for _, handler in ipairs(handlers) do
|
if(scope[value])then
|
||||||
handler(...)
|
return scope[value]
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function parsePropertyTag(node, element, scope)
|
|
||||||
local propertyConfig = element:getPropertyConfig(node.name)
|
|
||||||
|
|
||||||
if propertyConfig then
|
|
||||||
if propertyConfig.type == "table" then
|
|
||||||
local tableData = {}
|
|
||||||
|
|
||||||
for _, child in ipairs(node.children) do
|
|
||||||
if child.name == "item" or child.name == "entry" then
|
|
||||||
local entry = {}
|
|
||||||
|
|
||||||
for attr, value in pairs(child.attributes) do
|
|
||||||
if(colors[value])then
|
|
||||||
entry[attr] = colors[value]
|
|
||||||
else
|
|
||||||
entry[attr] = convertValue(value, "string", scope)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, prop in ipairs(child.children) do
|
|
||||||
if prop.name ~= "#text" and prop.name ~= "#cdata" then
|
|
||||||
if prop.children and #prop.children > 0 then
|
|
||||||
local firstChild = prop.children[1]
|
|
||||||
if firstChild.name == "#text" then
|
|
||||||
entry[prop.name] = convertValue(firstChild.value, "string", scope)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
local subEntry = {}
|
|
||||||
for subAttr, subValue in pairs(prop.attributes) do
|
|
||||||
subEntry[subAttr] = convertValue(subValue, "string", scope)
|
|
||||||
end
|
|
||||||
entry[prop.name] = next(subEntry) and subEntry or ""
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
table.insert(tableData, entry)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
element.set(node.name, tableData)
|
|
||||||
return true
|
|
||||||
else
|
else
|
||||||
local textNode = node.children[1]
|
errorManager.error("XMLParser: variable '" .. value .. "' not found in scope")
|
||||||
if textNode and textNode.name == "#text" then
|
end
|
||||||
element.set(node.name, convertValue(textNode.value, propertyConfig.type, scope))
|
end
|
||||||
return true
|
|
||||||
|
if value:match("^%s*<!%[CDATA%[.*%]%]>%s*$") then
|
||||||
|
local cdata = value:match("<!%[CDATA%[(.*)%]%]>")
|
||||||
|
local env = _ENV
|
||||||
|
for k,v in pairs(scope) do
|
||||||
|
env[k] = v
|
||||||
|
end
|
||||||
|
return load("return " .. cdata, nil, "bt", env)()
|
||||||
|
end
|
||||||
|
|
||||||
|
if value == "true" then
|
||||||
|
return true
|
||||||
|
elseif value == "false" then
|
||||||
|
return false
|
||||||
|
elseif colors[value] then
|
||||||
|
return colors[value]
|
||||||
|
elseif tonumber(value) then
|
||||||
|
return tonumber(value)
|
||||||
|
else
|
||||||
|
return value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function createTableFromNode(node, scope)
|
||||||
|
local list = {}
|
||||||
|
|
||||||
|
for _, child in pairs(node.children) do
|
||||||
|
if child.tag == "item" or child.tag == "entry" then
|
||||||
|
local item = {}
|
||||||
|
|
||||||
|
for attrName, attrValue in pairs(child.attributes) do
|
||||||
|
item[attrName] = convertValue(attrValue, scope)
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, subChild in pairs(child.children) do
|
||||||
|
if subChild.value then
|
||||||
|
item[subChild.tag] = convertValue(subChild.value, scope)
|
||||||
|
elseif #subChild.children > 0 then
|
||||||
|
item[subChild.tag] = createTableFromNode(subChild)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(list, item)
|
||||||
|
else
|
||||||
|
if child.value then
|
||||||
|
list[child.tag] = convertValue(child.value, scope)
|
||||||
|
elseif #child.children > 0 then
|
||||||
|
list[child.tag] = createTableFromNode(child)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return false
|
|
||||||
|
return list
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class Container
|
|
||||||
local Container = {}
|
local Container = {}
|
||||||
|
|
||||||
--- Loads and creates UI elements from XML content
|
|
||||||
--- @shortDescription Loads UI from XML string
|
|
||||||
--- @param self Container The container to load into
|
|
||||||
--- @param content string The XML content to parse
|
|
||||||
--- @param scope? table Optional scope for variable resolution
|
|
||||||
--- @return Container self The container instance
|
|
||||||
--- @usage
|
|
||||||
--- local xml = [[
|
|
||||||
--- <Frame>
|
|
||||||
--- <Button name="myButton" x="5" y="5"/>
|
|
||||||
--- </Frame>
|
|
||||||
--- ]]
|
|
||||||
--- container:loadXML(xml)
|
|
||||||
function Container:loadXML(content, scope)
|
function Container:loadXML(content, scope)
|
||||||
scope = scope or {}
|
scope = scope or {}
|
||||||
local tree = XMLParser.parse(content)
|
local nodes = XMLParser.parseText(content)
|
||||||
|
self:fromXML(nodes, scope)
|
||||||
|
end
|
||||||
|
|
||||||
local function createElements(nodes, parent, scope)
|
local baseFromXml
|
||||||
for _, node in ipairs(nodes.children) do
|
function Container.setup()
|
||||||
if node.name:sub(1,1) ~= "#" then
|
baseFromXml = require("elementManager").getElement("BaseElement").fromXML
|
||||||
if node.name:match("^on") then
|
end
|
||||||
handleEvent(node, parent, scope)
|
|
||||||
else
|
|
||||||
local handled = parsePropertyTag(node, parent, scope)
|
|
||||||
|
|
||||||
if not handled then
|
function Container:fromXML(content, scope)
|
||||||
local elementType = node.name:sub(1,1):upper() .. node.name:sub(2)
|
baseFromXml(self, content, scope)
|
||||||
local addMethod = "add"..elementType
|
for _, node in ipairs(content) do
|
||||||
|
local capitalizedName = node.tag:sub(1,1):upper() .. node.tag:sub(2)
|
||||||
|
if self["add"..capitalizedName] then
|
||||||
|
local element = self["add"..capitalizedName](self)
|
||||||
|
element:fromXML(node, scope)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
if not parent[addMethod] then
|
local BaseElement = {}
|
||||||
local parentType = parent.get and parent.get("type") or "Unknown"
|
function BaseElement:fromXML(node, scope)
|
||||||
errorManager.error(string.format(
|
if(node.attributes)then
|
||||||
'Tag <%s> is not valid inside <%s>',
|
for k, v in pairs(node.attributes) do
|
||||||
node.name, parentType:lower()
|
if(self._properties[k])then
|
||||||
))
|
self.set(k, convertValue(v, scope))
|
||||||
return
|
elseif self[k] then
|
||||||
end
|
if(k:sub(1,2)=="on")then
|
||||||
|
local val = v:gsub("\"", "")
|
||||||
local element = parent[addMethod](parent, node.attributes.name)
|
if(scope[val])then
|
||||||
|
self[k](self, scope[val])
|
||||||
for attr, value in pairs(node.attributes) do
|
else
|
||||||
local config = element:getPropertyConfig(attr)
|
errorManager.error("XMLParser: variable '" .. v .. "' not found in scope")
|
||||||
if config then
|
|
||||||
local convertedValue = convertValue(value, config.type, scope)
|
|
||||||
element.set(attr, convertedValue)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if #node.children > 0 then
|
|
||||||
createElements(node, element, scope)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
errorManager.error("XMLParser: property '" .. k .. "' not found in element '" .. self:getType() .. "'")
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
errorManager.error("XMLParser: property '" .. k .. "' not found in element '" .. self:getType() .. "'")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
createElements(tree, self, scope)
|
if(node.children)then
|
||||||
return self
|
for _, child in pairs(node.children) do
|
||||||
|
if(self._properties[child.tag])then
|
||||||
|
if(self._properties[child.tag].type == "table")then
|
||||||
|
self.set(child.tag, createTableFromNode(child, scope))
|
||||||
|
else
|
||||||
|
self.set(child.tag, convertValue(child.value, scope))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local args = {}
|
||||||
|
if(child.children)then
|
||||||
|
for _, child in pairs(child.children) do
|
||||||
|
if(child.tag == "param")then
|
||||||
|
table.insert(args, convertValue(child.value, scope))
|
||||||
|
elseif (child.tag == "table")then
|
||||||
|
table.insert(args, createTableFromNode(child, scope))
|
||||||
|
else
|
||||||
|
errorManager.error("XMLParser: unknown child '" .. child.tag .. "' in element '" .. self:getType() .. "'")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if(self[child.tag])then
|
||||||
|
if(#args > 0)then
|
||||||
|
self[child.tag](self, table.unpack(args))
|
||||||
|
elseif(child.value)then
|
||||||
|
self[child.tag](self, convertValue(child.value, scope))
|
||||||
|
else
|
||||||
|
self[child.tag](self)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
errorManager.error("XMLParser: method '" .. child.tag .. "' not found in element '" .. self:getType() .. "'")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
Container = Container
|
API = XMLParser,
|
||||||
|
Container = Container,
|
||||||
|
BaseElement = BaseElement
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,10 @@ local function findClassName(content)
|
|||||||
return content:match("%-%-%-@class%s+(%w+)")
|
return content:match("%-%-%-@class%s+(%w+)")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function findParentClass(content)
|
||||||
|
return content:match("%-%-%-@class%s+%w+%s*:%s*(%w+)")
|
||||||
|
end
|
||||||
|
|
||||||
local function parseProperties(content)
|
local function parseProperties(content)
|
||||||
local properties = {}
|
local properties = {}
|
||||||
for line in content:gmatch("[^\r\n]+") do
|
for line in content:gmatch("[^\r\n]+") do
|
||||||
@@ -80,7 +84,40 @@ local function collectAllClassNames(folder)
|
|||||||
return classes
|
return classes
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function getParentProperties(parentClass, allClasses)
|
||||||
|
-- Rekursiv alle Properties der Elternklasse(n) holen
|
||||||
|
local properties = {}
|
||||||
|
if parentClass then
|
||||||
|
for _, classContent in pairs(allClasses) do
|
||||||
|
if classContent.name == parentClass then
|
||||||
|
-- Properties der Elternklasse kopieren
|
||||||
|
for _, prop in ipairs(classContent.properties) do
|
||||||
|
table.insert(properties, prop)
|
||||||
|
end
|
||||||
|
-- Auch von der Elternklasse der Elternklasse holen
|
||||||
|
if classContent.parent then
|
||||||
|
local parentProps = getParentProperties(classContent.parent, allClasses)
|
||||||
|
for _, prop in ipairs(parentProps) do
|
||||||
|
table.insert(properties, prop)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return properties
|
||||||
|
end
|
||||||
|
|
||||||
local function generateClassContent(className, properties, combinedProperties, events, allClasses)
|
local function generateClassContent(className, properties, combinedProperties, events, allClasses)
|
||||||
|
-- Parent-Klasse finden
|
||||||
|
local parentClass = findParentClass(content)
|
||||||
|
-- Properties der Elternklasse(n) holen
|
||||||
|
local inheritedProps = getParentProperties(parentClass, allClasses)
|
||||||
|
-- Mit eigenen Properties kombinieren
|
||||||
|
for _, prop in ipairs(inheritedProps) do
|
||||||
|
table.insert(properties, prop)
|
||||||
|
end
|
||||||
|
|
||||||
if #properties == 0 and #events == 0 and className ~= "Container" then
|
if #properties == 0 and #events == 0 and className ~= "Container" then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user