模块:Wikitable.lua

来自「荏苒之境」
Sicusa留言 | 贡献2025年8月6日 (三) 01:03的版本
(差异) ←上一版本 | 最后版本 (差异) | 下一版本→ (差异)

此模块的文档可以在模块:Wikitable.lua/doc创建

local unpack = unpack or table.unpack

local BYTE_ENTER = string.byte('\r')
local BYTE_NEWLINE = string.byte('\n')
local BYTE_OPEN_CURLY = string.byte('{')
local BYTE_CLOSE_CURLY = string.byte('}')
local BYTE_SEPARATOR = string.byte('|')
local BYTE_EXCLAMATION = string.byte('!')
local BYTE_ADD = string.byte('+')
local BYTE_MINUS = string.byte('-')

local wikitable = {}

local function skip_to_next_line(str, pos)
    while true do
        local b = string.byte(str, pos)
        if b == BYTE_NEWLINE or b == BYTE_ENTER then
            return pos + 1
        elseif b == nil then
            return nil
        end
        pos = pos + 1
    end
end

local function find_character(str, pos, cbyte)
    while true do
        local b = string.byte(str, pos)
        if b == cbyte then
            return pos
        elseif b == nil then
            return nil
        end
        pos = pos + 1
    end
end

local function find_table_start(str, pos)
    while true do
        pos = find_character(str, pos, BYTE_OPEN_CURLY)
        if pos == nil then return nil end
        pos = pos + 1
        if string.byte(str, pos) == BYTE_SEPARATOR then
            return pos + 1
        end
    end
end

local function parse_cell(str, pos)
    local head = string.byte(str, pos)
    pos = pos + 1

    local bytes = {}
    while true do
        local b = string.byte(str, pos)
        if b == BYTE_SEPARATOR or b == BYTE_EXCLAMATION then
            break
        elseif b == nil then
            pos = nil
            break
        elseif b ~= BYTE_ENTER then
            bytes[#bytes+1] = b
        end
        pos = pos + 1
    end

    if bytes[#bytes] == BYTE_NEWLINE then
        bytes[#bytes] = nil
    end

    if head == BYTE_EXCLAMATION then
        return pos, "header", string.char(unpack(bytes))
    elseif head == BYTE_SEPARATOR then
        local fst_byte = bytes[1]
        if fst_byte == BYTE_ADD then
            return pos, "header_start"
        elseif fst_byte == BYTE_MINUS then
            return pos, "row_start"
        elseif fst_byte == BYTE_CLOSE_CURLY then
            return pos, "table_end"
        else
            return pos, "text", string.char(unpack(bytes))
        end
    end
end

wikitable.parse = function(str)
    local header = {}
    local rows = { header = header }
    local curr_row

    local pos = find_table_start(str, 1)
    if pos == nil then return rows end
    pos = skip_to_next_line(str, pos)
    local cell_type, cell_content

    while pos ~= nil do
        pos, cell_type, cell_content = parse_cell(str, pos)
        if cell_type == "header" then
            header[#header+1] = cell_content
        elseif cell_type == "row_start" then
            if curr_row ~= nil then
                rows[#rows+1] = curr_row
            end
            curr_row = {}
        elseif cell_type == "table_end" then
            if curr_row ~= nil then
                rows[#rows+1] = curr_row
            end
            break
        elseif cell_type == "text" then
            if curr_row == nil then
                curr_row = {}
            end
            curr_row[#curr_row+1] = cell_content
        end
    end

    return rows
end

return wikitable