diff --git a/src/xrfragment/Parser.lua b/src/xrfragment/Parser.lua deleted file mode 100644 index 088eac3..0000000 --- a/src/xrfragment/Parser.lua +++ /dev/null @@ -1,98 +0,0 @@ --- SPDX-License-Identifier: MPL-2.0 --- Copyright (c) 2023 Leon van Kammen/NLNET - -XF = {} - -function split (inputstr, sep) - if sep == nil then - sep = "%s" - end - local t={} - for str in string.gmatch(inputstr, "([^"..sep.."]+)") do - table.insert(t, str) - end - return t -end - -function XF.parse(key, value, resultMap) - local frag = {} - frag["prio"] = "^%d+$" - frag["pos"] = "(%d+),(%d+),(%d+)" - frag["q"] = ".+" - - if frag[key] ~= nil then - local regex = frag[key] - if string.match(value, regex) then - local v = Value:new() - XF.guessType(v, value) - if string.find(value, "|") then - v.args = {} - local args = split(value, "|") - for k, arg in ipairs(args) do - local x = Value:new() - XF.guessType(x, arg) - table.insert(v.args, x) - end - end - resultMap[key] = v; - else - error("[ i ] fragment '"..key.."' has incompatible value ("..value..")") - return false - end - else - error("[ i ] fragment '"..key.."' does not exist or has no type defined (yet)") - return false - end - return true -end - -function XF.guessType(v, str) - v.string = str - local parts = split(str, ",") - if #parts > 1 then - v.x = tonumber(parts[1]) - v.y = tonumber(parts[2]) - if #parts > 2 then - v.z = tonumber(parts[3]) - end - end - - if string.match(str, Type.isColor) then - v.color = str - end - if string.match(str, Type.isFloat) then - v.float = tonumber(str) - end - if string.match(str, Type.isInt) then - v.int_val = tonumber(str) - end -end - -Value = {} - -function Value:new() - local obj = {} - obj.x = nil - obj.y = nil - obj.z = nil - obj.color = nil - obj.string = nil - obj.int_val = nil - obj.float = nil - obj.args = nil - setmetatable(obj, self) - self.__index = self - return obj -end - -Type = {} -Type.isColor = "^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$" -Type.isInt = "^[0-9]+$" -Type.isFloat = "^[0-9]+%.[0-9]+$" -Type.isVector = "([,]+|%w)" - -local map = {} -XF.parse("pos","1,2,3",map) -print(map.pos.z) -XF.parse("pos","1,2,3|3,4,5",map) -print(map.pos.args[2].z) diff --git a/src/xrfragment/XRF.lua b/src/xrfragment/XRF.lua deleted file mode 100644 index 74a43e1..0000000 --- a/src/xrfragment/XRF.lua +++ /dev/null @@ -1,115 +0,0 @@ --- SPDX-License-Identifier: MPL-2.0 --- Copyright (c) 2023 Leon van Kammen/NLNET - -local XRF = {} - -XRF.ASSET = 1 -XRF.ASSET_OBJ = 2 -XRF.PV_OVERRIDE = 4 -XRF.QUERY_OPERATOR = 8 -XRF.PROMPT = 16 -XRF.ROUNDROBIN = 32 -XRF.NAVIGATOR = 64 -XRF.T_COLOR = 128 -XRF.T_INT = 256 -XRF.T_FLOAT = 512 -XRF.T_VECTOR2 = 1024 -XRF.T_VECTOR3 = 2048 -XRF.T_URL = 4096 -XRF.T_PREDEFINED_VIEW = 8192 -XRF.T_STRING = 16384 -XRF.T_STRING_OBJ = 32768 - -XRF.isColor = string.match("#([A-fa-f0-9]{6}|[A-fa-f0-9]{3})", "") -XRF.isInt = string.match("^[0-9]+$", "") -XRF.isFloat = string.match("^[0-9]+%.[0-9]+$", "") -XRF.isVector = string.match("([,]+|%w)", "") -XRF.isUrl = string.match("(://)?..*", "") -XRF.isUrlOrPretypedView = string.match("(^#|://)?..*", "") -XRF.isString = string.match(".+", "") - -function XRF.new(_fragment, _flags) - local self = {} - self.fragment = _fragment - self.flags = _flags - self.x = 0 - self.y = 0 - self.z = 0 - self.color = "" - self.string = "" - self.int = 0 - self.float = 0 - self.args = {} - self.query = {} - function self.is(flag) - print(flag) - print(self.flags) - return self.flags & flag ~= 0 - end - return self -end - -function XRF.set(flag, flags) - return flags | flag -end - -function XRF.unset(flag, flags) - return flags & ~flag -end - -function XRF.validate(self, value) - XRF.guessType(self, value) - if string.len(value:split("|")) then - self.args = {} - for i, val in ipairs(value:split("|")) do - local xrf = XRF.new(self.fragment, self.flags) - XRF.guessType(xrf, val) - table.insert(self.args, xrf) - end - end - if self.fragment == "q" then - --self.query = (new Query(value)):get() - end - if not isinstance(self.args, 'array') then - if self:is(XRF.T_VECTOR3) and (not isinstance(self.x, 'number') or not isinstance(self.y, 'number') or not isinstance(self.z, 'number')) then - ok = false - end - if self:is(XRF.T_VECTOR2) and (not isinstance(self.x, 'number') or not isinstance(self.y, 'number')) then - ok = false - end - if self:is(XRF.T_INT) and not isinstance(self.int, 'number') then - ok = false - end - end - return ok -end - -function XRF.guessType(v, str) - v.string = str - if string.len(str:split(",")) > 1 then - local xyz = string.split(str, ",") - if #xyz > 0 then - v.x = tonumber(xyz[1]) - end - if #xyz > 1 then - v.y = tonumber(xyz[2]) - end - if #xyz > 2 then - v.z = tonumber(xyz[3]) - end - end - if XRF.isColor:match(str) then - v.color = str - end - if XRF.isFloat:match(str) then - v.float = tonumber(str) - end - if XRF.isInt:match(str) then - v.int = tonumber(str) - end -end - -x = XRF.new("pos", XRF.ASSET | XRF.ROUNDROBIN ) -print( "roundrobin: " .. ( x.is( XRF.ROUNDROBIN ) and "ja" or "nee" ) ) - -return XRF diff --git a/src/xrfragment/xrfragment.lua b/src/xrfragment/xrfragment.lua new file mode 100644 index 0000000..ba79a8f --- /dev/null +++ b/src/xrfragment/xrfragment.lua @@ -0,0 +1,391 @@ +-- SPDX-License-Identifier: MPL-2.0 +-- Copyright (c) 2023 Leon van Kammen/NLNET +-- +-- This is a tiny minimal portable (re)implementation of the xrfragment parser +-- because sometimes this is easier to integrate compared to the fullfledged +-- HaXe generated code-languages (which requires various libraries) +-- +XRF = { + URI = {} +} + +----------------------------------------------------------------------------------- +--- global functions +----------------------------------------------------------------------------------- + +function split (inputstr, sep) + if sep == nil then + sep = "%s" + end + local t={} + for str in string.gmatch(inputstr, "([^"..sep.."]+)") do + table.insert(t, str) + end + return t +end + +function dump(t) + if( t == nil ) then return print("(nil)") end + for key, value in pairs(t) do + print(key, value) + end +end + +----------------------------------------------------------------------------------- +--- global types used across functions +----------------------------------------------------------------------------------- + +Value = {} +function Value:new() + local obj = {} + obj.x = nil + obj.y = nil + obj.z = nil + obj.color = nil + obj.string = nil + obj.int_val = nil + obj.float = nil + obj.args = nil + setmetatable(obj, self) + self.__index = self + return obj +end + +Type = {} +Type.isColor = "^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$" +Type.isInt = "^[0-9]+$" +Type.isFloat = "^[0-9]+%.[0-9]+$" +Type.isVector = "([,]+|%w)" + +----------------------------------------------------------------------------------- +--- Fragment Parser functions +----------------------------------------------------------------------------------- + +function XRF.parse(key, value, resultMap) + local frag = {} + frag["prio"] = "^%d+$" + frag["pos"] = "(%d+),(%d+),(%d+)" + frag["q"] = ".+" + + local regex = frag[key] + --if string.match(value, regex) then + local v = Value:new() + XRF.guessType(v, value) + resultMap[key] = v; + return true +end + +function XRF.guessType(v, str) + v.string = str + local parts = split(str, ",") + if #parts > 1 then + v.x = tonumber(parts[1]) + v.y = tonumber(parts[2]) + if #parts > 2 then + v.z = tonumber(parts[3]) + end + end + + if string.match(str, Type.isColor) then + v.color = str + end + if string.match(str, Type.isFloat) then + v.float = tonumber(str) + end + if string.match(str, Type.isInt) then + v.int_val = tonumber(str) + end +end + +----------------------------------------------------------------------------------- +--- URI helper class +----------------------------------------------------------------------------------- + +-- The MIT License (MIT) +-- +-- Copyright (c) 2011-2013 +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy +-- of this software and associated documentation files (the "Software"), to deal +-- in the Software without restriction, including without limitation the rights +-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +-- copies of the Software, and to permit persons to whom the Software is +-- furnished to do so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in +-- all copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +-- THE SOFTWARE. + +-- == list of known and common scheme ports +-- +-- @see http://www.iana.org/assignments/uri-schemes.html +-- +local SERVICES = { + http = 80, + https = 443, +} + +local LEGAL = { + ["-"] = true, ["_"] = true, ["."] = true, ["!"] = true, + ["~"] = true, ["*"] = true, ["'"] = true, ["("] = true, + [")"] = true, [":"] = true, ["@"] = true, ["&"] = true, + ["="] = true, ["+"] = true, ["$"] = true, [","] = true, + [";"] = true -- can be used for parameters in path +} + +-- aggressive caching of methods +local gsub = string.gsub +local char = string.char +local byte = string.byte +local upper = string.upper +local lower = string.lower +local format = string.format + +-- forward declaration of helper utilities +local util = {} + +local function decode(str) + local str = gsub(str, "+", " ") + + return (gsub(str, "%%(%x%x)", function(c) + return char(tonumber(c, 16)) + end)) +end + +local function encode(str) + return (gsub(str, "([^A-Za-z0-9%_%.%-%~])", function(v) + return upper(format("%%%02x", byte(v))) + end)) +end + +-- Build a URL given a table with the fields: +-- +-- - scheme +-- - user +-- - password +-- - host +-- - port +-- - path +-- - query +-- - fragment +-- +-- Example: +-- +-- local url = uri.build({ +-- scheme = "http", +-- host = "example.com", +-- path = "/some/path" +-- }) +-- +-- assert(url == "http://example.com/some/path") +-- +local function build(uri) + local url = "" + + if uri.path then + local path = uri.path + + gsub(path, "([^/]+)", function (s) return util.encode_segment(s) end) + + url = url .. tostring(path) + end + + if uri.query then + local qstring = tostring(uri.query) + if qstring ~= "" then + url = url .. "?" .. qstring + end + end + + if uri.host then + local authority = uri.host + + if uri.port and uri.scheme and SERVICES[uri.scheme] ~= uri.port then + authority = authority .. ":" .. uri.port + end + + local userinfo + + if uri.user and uri.user ~= "" then + userinfo = encode(uri.user) + + if uri.password then + userinfo = userinfo .. ":" .. encode(uri.password) + end + end + + if userinfo and userinfo ~= "" then + authority = userinfo .. "@" .. authority + end + + if authority then + if url ~= "" then + url = "//" .. authority .. "/" .. gsub(url, "^/+", "") + else + url = "//" .. authority + end + end + end + + if uri.scheme then + url = uri.scheme .. ":" .. url + end + + if uri.fragment then + url = url .. "#" .. uri.fragment + end + + return url +end + +-- Parse the url into the designated parts. +-- +-- Depending on the url, the following parts will be available: +-- +-- - scheme +-- - userinfo +-- - user +-- - password +-- - authority +-- - host +-- - port +-- - path +-- - query +-- - fragment +-- +-- Usage: +-- +-- local u = uri.parse("http://john:monkey@example.com/some/path#h1") +-- +-- assert(u.host == "example.com") +-- assert(u.scheme == "http") +-- assert(u.user == "john") +-- assert(u.password == "monkey") +-- assert(u.path == "/some/path") +-- assert(u.fragment == "h1") +-- +local function parse(url) + local uri = { query = nil } + + util.setauthority(uri, "") + + local url = tostring(url or "") + + url = gsub(url, "#(.*)$", function(v) + uri.fragment = v + uri.frag = {} + for part in v:gmatch("([^&]+)") do + if part:match("=") then + local k,v = string.match(part,"(.-)=(.-)$") + XRF.parse( k, v, uri.frag ) + else + XRF.parse( part, " ", uri.frag ) + end + end + return "" + end) + + url = gsub(url, "^([%w][%w%+%-%.]*)%:", function(v) + uri.scheme = lower(v) + return "" + end) + + url = gsub(url, "%?(.*)", function(v) + uri.query = v + return "" + end) + + url = gsub(url, "^//([^/]*)", function(v) + util.setauthority(uri, v) + return "" + end) + + uri.path = decode(url) + + return uri +end + +function util.encode_segment(s) + local function encode_legal(c) + if LEGAL[c] then + return c + end + + return encode(c) + end + + return gsub(s, "([^a-zA-Z0-9])", encode_legal) +end + +-- set the authority part of the url +-- +-- The authority is parsed to find the user, password, port and host if available. +-- @param authority The string representing the authority +-- @return a string with what remains after the authority was parsed +function util.setauthority(uri, authority) + uri.authority = authority + uri.port = nil + uri.host = nil + uri.userinfo = nil + uri.user = nil + uri.password = nil + + authority = gsub(authority, "^([^@]*)@", function(v) + uri.userinfo = decode(v) + return "" + end) + + authority = gsub(authority, "^%[[^%]]+%]", function(v) + -- ipv6 + uri.host = v + return "" + end) + + authority = gsub(authority, ":([^:]*)$", function(v) + uri.port = tonumber(v) + return "" + end) + + if authority ~= "" and not uri.host then + uri.host = lower(authority) + end + + if uri.userinfo then + local userinfo = uri.userinfo + + userinfo = gsub(userinfo, ":([^:]*)$", function(v) + uri.password = v + return "" + end) + + uri.user = userinfo + end + + return authority +end + +XRF.URI = { + build = build, + parse = parse, + encode = encode, + decode = decode +} + +----------------------------------------------------------------------------------- +--- some tests +----------------------------------------------------------------------------------- + +local map = {} +XRF.parse("pos","1,2,3",map) +print(map.pos.z) +local URI = XRF.URI.parse("https://foo.com/flop.glb#foo=1&bar=flop") +dump(URI) +dump(URI.frag.foo) +dump(URI.frag.bar)