3

我有这种形式的路径定义(示例):

<path d="M 20 30 L 20 20 20 40 40 40"/>

在 Lua 中,它变成:

"M 20 30 L 20 20 20 40 40 40"

我怎么能在纯 Lua 中解析它以获得类似的东西:

{'M', 20, 30, 'L', 20, 20, 20, 40, 40, 40 }

或者,完美:

{{'M', 20, 30}, {'L', 20, 20}, {'L', 20, 40}, {'L', 40, 40}}

Lua 模式有这样的能力吗?

编辑:我想涵盖所有有效的 SVG 路径,或者至少是 Inkscape 生成的。 规范 inkscape 生成的路径

4

3 回答 3

3

不是直接的,你当然需要一个简化的解析器。

好奇心战胜了我,尽管我通常不喜欢“为我做这项工作”的帖子

--- Parse svg `path` attribute into 2-D array
function parsePath(input)
    local output, line = {}, {};
    output[#output+1] = line;

    input = input:gsub("([^%s,;])([%a])", "%1 %2"); -- Convert "100D" to "100 D"
    input = input:gsub("([%a])([^%s,;])", "%1 %2"); -- Convert "D100" to "D 100"
    for v in input:gmatch("([^%s,;]+)") do
        if not tonumber(v) and #line > 0 then
            line = {};
            output[#output+1] = line;
        end
        line[#line+1] = v;
    end
    return output;
end

-- Test output
local input = 'M20 30L20 20,20 40;40 40 X1 2 3 12.8z';
local r = parsePath(input);
for i=1, #r do
    print("{ "..table.concat(r[i], ", ").." }");
end

由于 Inkscape 似乎总是在指令和数字之间放置一个空格,因此如果您只解析 Inkscape 生成的文件,则可以省略两个 gsub 行。

该函数还会丢弃 Inkscape 喜欢放入路径定义的大多数随机字符,但是如果您真的想阅读所有符合标准的路径定义,可能会有一些细节需要您解决。

更新(在浏览SVG BNF 定义之后)

SVG 标准声明Superfluous white space and separators such as commas can be eliminated,但是查看 BNF 表示法我找不到除空格和逗号之外的任何其他分隔符。

因此,您可能可以将第二个正则表达式更改为"([^%a%d%.eE-]+)". 但我认为以下功能更适合:

function parsePath(input)
    local out = {};

    for instr, vals in input:gmatch("([a-df-zA-DF-Z])([^a-df-zA-DF-Z]*)") do
        local line = { instr };
        for v in vals:gmatch("([+-]?[%deE.]+)") do
            line[#line+1] = v;
        end
        out[#out+1] = line;
    end
    return out;
end

-- Test output
local input = 'M20-30L20,20,20X40,40-40H1,2E1.7 1.8e22,3,12.8z';
local r = parsePath(input);
for i=1, #r do
    print("{ "..table.concat(r[i], ", ").." }");
end

这个函数非常宽松,因为它允许忽略任何不必要的空白,并且不验证任何语义,除了它会丢弃第一个不是eor的字母之前的任何数据E

它还将默默地忽略任何不匹配的数据。

如果您只想匹配现有指令,可以将模式替换([a-df-zA-DF-Z])([^a-df-zA-DF-Z]*)([MmZzLlHhVvCcSsQqTtAa])([^MmZzLlHhVvCcSsQqTtAa]*). 但是,这将导致不存在指令的所有值都添加到前一条指令中,因此我认为这不是一个好主意,最好在稍后解析超集并引发语义错误。

于 2013-06-02T15:18:43.847 回答
2
local path = 'M 20 30 L 20 20 20 40 40 40'

local s, t = '', {}
for c, x, y in path:gmatch'(%a?)%s*(%d+)%s*(%d+)' do
   s = (s..c):sub(-1)
   t[#t+1] = {s, tonumber(x), tonumber(y)}
end
-- Now t == {{'M', 20, 30}, {'L', 20, 20}, {'L', 20, 40}, {'L', 40, 40}}
于 2013-05-31T18:44:51.857 回答
0

我喜欢 Egor 的解决方案,但它不适用于小数和字母 V 和 H,所以:

local function parsePath (input)
    input = input:gsub("([^%s,;])([%a])", "%1 %2") -- Convert "100D" to "100 D"
    input = input:gsub("([%a])([^%s,;])", "%1 %2") -- Convert "D100" to "D 100"
    local output, line = {}
    for v in input:gmatch("([^%s,;]+)") do
        if tonumber(v) then
            line[#line+1] = math.floor(tonumber(v)+0.5)
        else
            line = {v}
            output[#output+1] = line
        end
    end
    return output
end

运行:

local ds = {
    "M40,360H-40", -- line
    "M -40,480 H 40", -- line
    "M 840,1000 V 920 M 720,920 V 1000", -- two lines
    "M 1280.1,-39.9 1200.1,39.9", -- line with decimals
    "M 1320,40 1400,-40",
    "M 40,480 H 360",
    "M 760,360 400,360",
    "M 1120,240 1320,40",
    "M 400,360 40,360",
    
    "M 840,920 C 840,680 1040,320 1120,240", -- cubic bezier
    "M 1200,40 C 1160,80 1120,120 1080,160",
    "M 1080,160 C 920,320 520,360 400,360",
    "M 360,480 C 520,480 720,760 720,920",
    "M 760,360 C 640,360 560,520 640,600",
    "M 640,600 C 720,680 840,640 880,560",
    "M 880,560 C 920,480 880,360 760,360",
    "M 1080,160 C 1040,200 920,360 760,360",
    "M 360,480 C 480,480 560,520 640,600",
    "M 640,600 C 720,680 720,840 720,920",
    "M 840,920 C 840,800 840,640 880,560",
    "M 880,560 C 920,480 1120,240 1120,240",
    
    "M 0,600 H 360 L 600,840 V 960 H 0",
    "M 1440,0 H 1920 V 960 H 960 V 720",
    "M 0,0 H 1080 L 840,240 H 0"
}

for i, d in ipairs (ds) do
    local parsedPath = parsePath (d)
    local str = '{'
    for i, component in ipairs (list) do 
        str = str .. '{'.. table.concat(component, ',') ..'},'
    end
    str = str:sub(1, -2) -- remove last comma
    str = str .. '}'
    print (str)
end

结果:

{{M,40,360},{H,-40}}
{{M,-40,480},{H,40}}
{{M,840,1000},{V,920}}
{{M,720,920},{V,1000}}
{{M,1200,-40},{V,40}}
{{M,1320,40},{V,-40}}
{{M,840,920},{C,840,800,1000,560,1080,480},{M,1080,480},{C,1160,400,1320,240,1320,40}}
{{M,1200,40},{C,1200,120,1160,160,1080,200},{M,1080,200},{C,920,280,720,360,480,360},{M,480,360},{C,3360,360,120,360,40,360}}
{{M,40,480},{C,40,480,240,480,360,480,520,480,720,760,720,920}}
{{M,760,400},{C,640,400,560,520,640,600}}
{{M,640,600},{C,720,680,800,640,840,600}}
{{M,840,600},{C,880,560,880,400,760,400}}
{{M,1080,200},{C,920,280,920,400,760,400}}
{{M,760,400},{C,640,400,600,360,480,360}}
{{M,360,480},{C,480,480,560,520,640,600}}
{{M,640,600},{C,720,680,720,840,720,920}}
{{M,840,920},{C,840,800,760,680,840,600}}
{{M,840,600},{C,880,560,1040,520,1080,480}}
{{M,0,0},{V,240},{H,720},{C,720,240,1080,120,1080,120},{V,0}}
{{M,0,0},{H,1080},{V,120},{L,840,240},{H,0}}
{{M,0,600},{H,360},{L,600,840},{V,960},{H,0}}
{{M,1440,0},{H,1920},{V,960},{H,960},{V,840},{L,1200,800,1400,600,1440,360}}
于 2021-12-25T21:52:17.247 回答