コンテンツにスキップ

モジュール:Kanbun

出典: フリー教科書『ウィキブックス(Wikibooks)』

漢文訓読文を表示するためのモジュールです。

使用法

[編集]

例:

{{#invoke:Kanbun|kanbun|
桃之夭夭タル\O灼灼タリ其ノ華\\
之ノ子于キ帰グ\O宜シカラン[二]其{ノ}室家ニ[一]\\\\

桃之ノ夭夭タル\O有リ[レ]蕡タル其ノ実\\
之ノ子于キ帰グ\O宜シカラン[二]其ノ家室ニ[一]\\\\

桃之夭夭タル\O其ノ葉蓁蓁タリ\\
之ノ子于キ帰グ\O宜シカラン[二]其ノ家人ニ[一]
}}

出力:

タルタリ
シカラン

タルタル
シカラン

タルタリ
シカラン

送り仮名と読み仮名は{}で囲むか、漢字の直後に書きます。[]で囲んだ部分は返り点を表します。 入力中の改行は漢字の区切り部分であれば出力に影響しません。出力で改行を挿入する場合は、\\を使用します。

特別な記号

  • \\:改行を挿入する。
  • \O:空白を挿入する。

local p = {}

local kunten = {
    ["レ"] = "㆑", -- 返り点には U+3191からU+319Fを使う。
    ["一"] = "㆒",
    ["二"] = "㆓",
    ["三"] = "㆔",
    ["四"] = "㆕",
    ["上"] = "㆖",
    ["中"] = "㆗",
    ["下"] = "㆘",
    ["甲"] = "㆙",
    ["乙"] = "㆚",
    ["丙"] = "㆛",
    ["丁"] = "㆜",
    ["天"] = "㆝",
    ["地"] = "㆞",
    ["人"] = "㆟",
    ["一レ"] = "㆒㆑",
    ["上レ"] = "㆖㆑",
    ["甲レ"] = "㆙㆑",
    ["天レ"] = "㆝㆑"
}


local function normalize(input_text)
    input_text = input_text:gsub("{", "{")
    input_text = input_text:gsub("}", "}")
    input_text = input_text:gsub("[", "[")
    input_text = input_text:gsub("]", "]")
    input_text = input_text:gsub("。", "。")
    input_text = input_text:gsub("、", "、")
    input_text = input_text:gsub("(", "(")
    input_text = input_text:gsub(")", ")")
    input_text = input_text:gsub("「", "「")
    input_text = input_text:gsub("」", "」")
    input_text = input_text:gsub("[\r\n]", "")
    return input_text
end

local function parse(input)
    local i = 1
    local j = 1
    local res
    return function()
        while j <= #input do
            i = j
            local byte1 = input:byte(j)

            if byte1 == 0x20 or byte1 == 0xa then
                j = j + 1

            elseif byte1 == 0x5c then -- バックスラッシュ
                local next_char = input:sub(j + 1, j + 1)

                if next_char == "\\" then
                    res = input:sub(i, j + 1)
                    j = j + 2
                    return res

                elseif next_char == "O" then
                    res = input:sub(i, j + 1)
                    j = j + 2
                    return res
                    
                elseif next_char == "R" then
                    local s, e = input:find("\\R%b{}%b{}", i)
                    res = input:sub(i,e+1)
                    j = e + 1
                    return res
                   
                elseif next_char == "B" then
                    local s, e = input:find("\\B%b{}%b{}", i)
                    res = input:sub(i,e)
                    j = e + 1
                    return res
                    
                else
                    local brace, pos = input:find("{.-}", j + 1)
                    if not pos then
                        res = input:sub(i, j + 1)
                        j = j + 2
                        return res
                    end

                    res = input:sub(i, pos)
                    j = pos + 1
                    return res
                end

            else
                if input:sub(i, i + 2) == "「" then
                    j = i + 3
                end
                if input:sub(i, i + 2) == "『" then
                    j = i + 3
                end

                if byte1 >= 0xF0 then
                    j = j + 3
                elseif byte1 >= 0xE0 then
                    j = j + 2
                elseif byte1 >= 0xC0 then
                    j = j + 1
                end

                -- IVS
                local byteo = input:byte(j + 1)
                if byteo and byteo >= 0xF0 then
                    local byte2 = input:byte(j + 2) or 0
                    local byte3 = input:byte(j + 3) or 0
                    local byte4 = input:byte(j + 4) or 0
                    local codepoint = (byteo - 0xF0) * 0x40000 + (byte2 - 0x80) * 0x1000 + (byte3 - 0x80) * 0x40 + (byte4 - 0x80)
                    if codepoint >= 0xE0100 and codepoint <= 0xE01EF then
                        j = j + 4
                    end
                end

		local bytek = input:byte(j+1)
		local nami = 0
                while bytek and bytek >= 0xE0 and bytek <= 0xEF do
                        local second = input:byte(j + 2) or 0
                        local third = input:byte(j + 3) or 0
                        local codepoint = ((bytek % 16) * 4096) + ((second % 64) * 64) + (third % 64)
                        if codepoint >= 0x3041 and codepoint <= 0x30FF then
				if nami == 0 then
                                        input = input:sub(1,j) .. "{" .. input:sub(j+1)
					j = j + 1
					nami = 1
				end
                                j = j + 3
			        bytek = input:byte(j+1)
				if not (bytek and bytek >= 0xE0 and bytek <= 0xEF) then
					if nami == 1 then
                                                input = input:sub(1,j) .. "}" .. input:sub(j+1)
                                                j = j + 1
                                                nami = 0
                                        end
				end
			else
				if nami == 1 then
                                        input = input:sub(1,j) .. "}" .. input:sub(j+1)
                                        j = j + 1
                                        nami = 0
				end
				break
			end
		end


                local next_char = input:sub(j + 1, j + 1)

                while next_char == "[" or next_char == "{" or next_char == "(" do
                    if next_char == "[" then
                        j = input:find("]", j, true)
                    elseif next_char == "{" then
                        j = input:find("}", j, true)
                    elseif next_char == "(" then
                        j = input:find(")", j, true)
                    end

                    next_char = input:sub(j + 1, j + 1)
                end

                if input:sub(j + 1, j + 6) == "。」" then
                    j = j + 6
                elseif input:sub(j + 1, j + 6) == "。』" then
                    j = j + 6
                elseif input:sub(j + 1, j + 3) == "。" then
                    j = j + 3
                elseif input:sub(j + 1, j + 3) == "、" then
                    j = j + 3
                elseif input:sub(j + 1, j + 3) == "」" then
                    j = j + 3
                elseif input:sub(j + 1, j + 3) == "』" then
                    j = j + 3
                elseif input:sub(j + 1, j + 3) == "㆐" then
                    j = j + 3
                else
                end

                res = input:sub(i, j)
                j = j + 1
                return res
            end
        end
        
    end
end


local col = -1
local shift = -1
local style = nil

function kanbun(input_text, colmax)
    local mozi = {}
    for char in parse(input_text) do
        local kanzi = char:match("^(.-)[\\%[%{]") or char
        kanzi = kanzi:gsub("。", "")
        kanzi = kanzi:gsub("、", "")
        kanzi = kanzi:gsub("「", "")
        kanzi = kanzi:gsub("」", "")
        kanzi = kanzi:gsub("『", "")
        kanzi = kanzi:gsub("』", "")
        kanzi = kanzi:gsub("㆐", "")
        local okurigana = char:match("{(.-)}")
        local kaeriten = char:match("%[(.-)%]")
        local saidoku = char:match("%((.-)%)")
        local symbol = char:match("\\(B)") or char:match("\\(R)") or  char:match("\\(begin)") or char:match("\\(end)") or char:match("\\(.)")


        if symbol == "O" then
            if col == 0 then
                mozi[#mozi+1] = '<span style="display: inline-block; position: relative; margin-left: 0.5em; margin-right: 0.5em; margin-top: 0em; margin-bottom: 1.9em;"></span>'
            else
                mozi[#mozi+1] = '<span style="display: inline-block; position: relative; margin-left: 0.5em; margin-right: 0.5em; margin-top: 0em; margin-bottom: 0.9em;"></span>'
            end
            col = col + 1
            
        elseif symbol == "\\" then
            mozi[#mozi+1] = "<br>"
            shift = 0
            col = 0

        elseif symbol == "begin" then
            local style = char:match("\\begin{(.-)}")
            style = style:gsub("~", " ")
            mozi[#mozi+1] = string.format('<span style="padding-right:0.5em; %s">', style)

        elseif symbol == "end" then
            mozi[#mozi+1] = "</span>"

        elseif symbol == "R" then
            local sen, ruby = char:match("\\R%{([^}]*)%}%{([^}]*)%}")
            mozi[#mozi+1] = [[<span style="position:relative;display:inline-block;">]]
            mozi[#mozi+1] = kanbun(sen, colmax)
            local rubi = {}
            for i = 1, #ruby do
                if (i - 1) % 3 == 0 then
                    local hira = string.sub(ruby, i, i + 2)
                    table.insert(rubi, "<span>" .. hira .. "</span>")
                end
            end
            mozi[#mozi+1] = [[<span style="position:absolute;right:0.5em;top:0;bottom:0;display:flex;flex-direction:column;justify-content:space-between;font-size:0.5em;writing-mode:horizontal-tb;margin-block:0.6em;margin-block-end:1em;">]] .. table.concat(rubi) .. [[</span>]]
            mozi[#mozi+1] = [[</span>]]
            
        elseif symbol == "B" then
            local a1s, a1e = char:find("%b{}", 3)
            local a2s, a2e = char:find("%b{}", a1e + 1)
            local sen = char:sub(a1s + 1, a1e - 1)
            local ban = char:sub(a2s + 1, a2e - 1)

            mozi[#mozi+1] = [[<span style="padding-right: 0.5em; background: linear-gradient(to right, transparent 95%, black 95%, black 98%, transparent 98% ); box-decoration-break: clone; -webkit-box-decoration-break: clone;">]]
            mozi[#mozi+1] = string.format('<span style="position:absolute;writing-mode: horizontal-tb;text-orientation: upright;font-size: 50%%;transform: translate(1.2em, -0.2em);">%s</span>', ban)
            mozi[#mozi+1] = kanbun(sen, colmax)
            mozi[#mozi+1] = "</span>"            
        else
            -- 漢字
            if not style then
                mozi[#mozi+1] = string.format(
                    '<span style="display: inline-block; position: relative; margin-left: 0.5em; margin-right: 0.5em; bottom: -%sem; %s">%s',
                    shift, style, kanzi)
                col = col + 1
            else
                mozi[#mozi+1] = string.format(
                    '<span style="display: inline-block; position: relative; margin-left: 0.5em; margin-right: 0.5em; bottom: -%sem;">%s',
                    shift, kanzi)
                col = col + 1
            end
            -- 返り点
            if kaeriten then
                kaeriten = kunten[kaeriten] or kaeriten

                if kaeriten == "㆒㆑" then
                    mozi[#mozi+1] = [[<span style="position: absolute; font-size: 50%; bottom: -0.6em; left: 0.25em;">㆒</span><span style="position: absolute; font-size: 50%; bottom: -0.95em; left: 0.25em;">㆑</span>]]

                elseif kaeriten == "㆖㆑" then
                    mozi[#mozi+1] = [[<span style="position: absolute; font-size: 50%; bottom: -0.9em; left: 0.25em; transform: scaleY(0.6);">㆖</span><span style="position: absolute; font-size: 50%; bottom: -1.3em; left: 0.25em; transform: scaleY(0.6);">㆑</span>]]

                elseif kaeriten == "㆙㆑" then
                    mozi[#mozi+1] = [[<span style="position: absolute; font-size: 50%; bottom: -0.9em; left: 0.25em; transform: scaleY(0.6);">㆙</span><span style="position: absolute; font-size: 50%; bottom: -1.3em; left: 0.25em; transform: scaleY(0.6);">㆑</span>]]

                elseif kaeriten == "㆝㆑" then
                    mozi[#mozi+1] = [[<span style="position: absolute; font-size: 50%; bottom: -0.9em; left: 0.25em; transform: scaleY(0.6);">㆝</span><span style="position: absolute; font-size: 50%; bottom: -1.3em; left: 0.25em; transform: scaleY(0.6);">㆑</span>]]

                elseif kaeriten == "ニ" then 
                    mozi[#mozi+1] = [[<span style="position: absolute; font-size: 50%; bottom: -1em; left: 0.25em;color:red">ニ</span>]]

                else
                    mozi[#mozi+1] = string.format(
                            '<span style="position: absolute; font-size: 50%%; bottom: -1em; left: 0.25em;">%s</span>',
                            kaeriten)
                end

            end

            -- 送り仮名
            if okurigana then

                -- ひらがなとカタカナの個数を計算
                local hiraganumber = 0
                local katakanumber = 0

                local i = 1
                while i <= #okurigana do
                    local byte = string.byte(okurigana, i)
                    local second = string.byte(okurigana, i + 1) or 0
                    local third = string.byte(okurigana, i + 2) or 0

                    local codepoint = ((byte % 16) * 4096) + ((second % 64) * 64) + (third % 64)

                    if codepoint >= 0x3041 and codepoint <= 0x309F then
                        hiraganumber = hiraganumber + 1
                    elseif codepoint >= 0x30A0 and codepoint <= 0x30FF then
                        katakanumber = katakanumber + 1
                    end

                    i = i + 3
                end

                -- 位置の調整

                if hiraganumber == 0 and katakanumber == 5 then
                    bottom = "-1"
                    local first_line = okurigana:sub(1, 9)
                    local second_line = okurigana:sub(10)

                    mozi[#mozi+1] = string.format(
                        '<span style="position: absolute; font-size: 50%%; bottom: %sem; right: -1.5em; white-space: nowrap;">%s</span>',
                        bottom, first_line)

                    mozi[#mozi+1] = string.format(
                        '<span style="position: absolute; font-size: 50%%; bottom: %sem; right: -0.5em; white-space: nowrap;">%s</span>',
                        bottom, second_line)
                    shift = shift + 0.1

                elseif hiraganumber == 0 and katakanumber == 1 then
                    mozi[#mozi+1] = string.format(
                        '<span style="position: absolute; font-size: 50%%; bottom: -0.5em; right: -0.5em; white-space: nowrap;">%s</span>',
                        okurigana)

                elseif hiraganumber == 0 and katakanumber == 2 then
                    mozi[#mozi+1] = string.format(
                        '<span style="position: absolute; font-size: 50%%; bottom: -1em; right: -0.5em; white-space: nowrap;">%s</span>',
                        okurigana)

                elseif hiraganumber == 0 and katakanumber == 3 then
                    mozi[#mozi+1] = string.format(
                        '<span style="position: absolute; font-size: 50%%; bottom: -2em; right: -0.5em; white-space: nowrap;">%s</span>',
                        okurigana)
                    shift = shift + 0.1

                elseif hiraganumber == 0 and katakanumber == 4 then
                    local first_line = okurigana:sub(1, 6)
                    local second_line = okurigana:sub(7)

                    mozi[#mozi+1] = string.format(
                        '<span style="position: absolute; font-size: 50%%; bottom: -0.5em; right: -1.5em; white-space: nowrap;">%s</span>',
                        first_line)

                    mozi[#mozi+1] = string.format(
                        '<span style="position: absolute; font-size: 50%%; bottom: -1.5em; right: -0.5em; white-space: nowrap;">%s</span>',
                        second_line)

                elseif hiraganumber == 1 and katakanumber == 0 then
                    mozi[#mozi+1] = string.format(
                        '<span style="position: absolute; font-size: 50%%; bottom: 0.5em; right: -0.5em; white-space: nowrap;">%s</span>',
                        okurigana)

                elseif hiraganumber == 2 and katakanumber == 0 then
                    mozi[#mozi+1] = string.format(
                        '<span style="position: absolute; font-size: 50%%; bottom: 0em; right: -0.5em; white-space: nowrap;">%s</span>',
                        okurigana)
                    

                elseif hiraganumber == 3 and katakanumber == 0 then
                    mozi[#mozi+1] = string.format(
                        '<span style="position: absolute; font-size: 50%%; bottom: -0.5em; right: -0.5em; white-space: nowrap;">%s</span>',
                        okurigana)

                elseif hiraganumber == 4 and katakanumber == 0 then
                    mozi[#mozi+1] = string.format(
                        '<span style="position: absolute; font-size: 50%%; bottom: -1em; right: -0.5em; white-space: nowrap;">%s</span>',
                        okurigana)

                elseif hiraganumber == 1 and katakanumber == 1 then
                    mozi[#mozi+1] = string.format(
                        '<span style="position: absolute; font-size: 50%%; bottom: -0.5em; right: -0.5em; white-space: nowrap;">%s</span>',
                        okurigana)

                elseif hiraganumber + katakanumber == 4 then
                    mozi[#mozi+1] = string.format(
                        '<span style="position: absolute; font-size: 50%%; top: -0.5em; right: -0.5em; white-space: nowrap; transform: scaleY(0.85);">%s</span>',
                        okurigana)

                elseif hiraganumber + katakanumber >= 5 then
                    shift = shift + 0.5 * 0.8 * (hiraganumber + katakanumber - 4.5)
                    mozi[#mozi+1] = string.format(
                        '<span style="position: absolute; font-size: 50%%; top: -0.5em; right: -0.5em; white-space: nowrap; transform: scaleY(0.8);">%s</span>',
                        okurigana)

                else
                    mozi[#mozi+1] = string.format(
                        '<span style="position: absolute; font-size: 50%%; bottom: -1em; right: -0.5em; white-space: nowrap;">%s</span>',
                        okurigana)
                end
            end

                        if char:find("。」") then
                mozi[#mozi+1] = [[<span style="position: absolute; bottom: -1em;">。</span><span style="position: absolute; bottom: -1.25em;">」</span>]]

            elseif char:find("」") then
                mozi[#mozi+1] = [[<span style="position: absolute; bottom: -1em;">」</span>]]
            end

            if char:find("。』") then
                mozi[#mozi+1] = [[<span style="position: absolute; bottom: -1em;">。</span><span style="position: absolute; bottom: -1.25em;">』</span>]]

            elseif char:find("』") then
                mozi[#mozi+1] = [[<span style="position: absolute; bottom: -1em;">』</span>]]
            end

            if char:find("、") then
                mozi[#mozi+1] = [[<span style="position: absolute; bottom: -1em;">、</span>]]
            end

            if char:find("。") then
                mozi[#mozi+1] = [[<span style="position: absolute; bottom: -1em;">。</span>]]
            end

            if char:find("「") then
                mozi[#mozi+1] = [[<span style="position: absolute; top: -1em;">「</span>]]
            end

            if char:find("『") then
                mozi[#mozi+1] = [[<span style="position: absolute; top: -1em;">『</span>]]
            end
            if char:find("㆐") then
                mozi[#mozi+1] = [[<span style="position: absolute; bottom: -0.8em; transform: scale(0.5, 0.7);">㆐</span>]]
            end

            if saidoku then
                local hiraganumber = 0
                local katakanumber = 0

                local i = 1
                while i <= #saidoku do
                    local byte = string.byte(saidoku, i)
                    local second = string.byte(saidoku, i + 1) or 0
                    local third = string.byte(saidoku, i + 2) or 0

                    local codepoint = ((byte % 16) * 4096) + ((second % 64) * 64) + (third % 64)

                    if codepoint >= 0x3041 and codepoint <= 0x309F then
                        hiraganumber = hiraganumber + 1
                    elseif codepoint >= 0x30A0 and codepoint <= 0x30FF then
					katakanumber = katakanumber + 1
                    end

                    i = i + 3
                end

                local bottom = nil
                local top = nil

                if hiraganumber == 0 and katakanumber == 1 then
                    bottom = "-0.5"

                elseif hiraganumber == 0 and katakanumber == 2 then
                    bottom = "-1"

                elseif hiraganumber == 0 and katakanumber == 3 then
                    bottom = "-2"

                elseif hiraganumber == 0 and katakanumber == 4 then
                    bottom = "-2.5"

                elseif hiraganumber == 1 and katakanumber == 0 then
                    bottom = "0.5"

                elseif hiraganumber == 2 and katakanumber == 0 then
                    bottom = "0"

                elseif hiraganumber == 3 and katakanumber == 0 then
                    bottom = "-0.5"

                elseif hiraganumber == 4 and katakanumber == 0 then
                    bottom = "-1"

                elseif hiraganumber == 1 and katakanumber == 1 then
                    bottom = "-0.5"

                elseif hiraganumber + katakanumber >= 5 then
                    top = "0"
                    shift = shift + 0.5 * (hiraganumber + katakanumber - 4)

                else
                    bottom = "-1"
                end

                if bottom then
                    mozi[#mozi+1] = string.format(
                        '<span style="position: absolute; font-size: 50%%; bottom: %sem; left: -0.5em; white-space: nowrap;">%s</span>',
                        bottom, saidoku)
                else
                    mozi[#mozi+1] = string.format(
                        '<span style="position: absolute; font-size: 50%%; top: %sem; left: -0.5em; white-space: nowrap;">%s</span>',
                        top, saidoku)
                end
            end
            

            mozi[#mozi+1] = "</span>"

            if col < colmax then
                mozi[#mozi+1] = '<span style="display: inline-block; position: relative; margin-left: 0.5em; margin-right: 0.5em; margin-top: 0em; margin-bottom: 0.9em;"></span>'
            end

        end

        if col >= colmax then
            mozi[#mozi+1] = "<br>"
            shift = 0
            col = 0
        end
    end
    return table.concat(mozi)
end



function p.kanbun(frame)
    local input_text = frame.args[1]
    local colmax = tonumber(frame.args.col) or 99
    input_text = normalize(input_text)
    local syuturyoku = [[<div style="writing-mode: vertical-rl; font-size: 150%; margin-top:0.6em; margin-bottom:0.6em; white-space: nowrap; font-family: 'Noto Serif CJK JP', serif">]]
    if col == -1 then
        col = 0
    end
    if shift == -1 then
        shift = 0
    end
    if style == nil then
        style = ""
    end
    syuturyoku = syuturyoku .. kanbun(input_text, colmax)
    syuturyoku = syuturyoku .. "</div>"
    return syuturyoku
end

return p