七日内新公告:全站维护公告
  • 你好~!欢迎来到萌娘百科镜像站!如需查看或编辑,请联系本站管理员注册账号。
  • 本镜像站和其他萌娘百科的镜像站无关,请注意分别。

Module:LoveLive

猛汉♂百科,万男皆可猛的百科全书!转载请标注来源页面的网页链接,并声明引自猛汉百科。内容不可商用。
跳到导航 跳到搜索
Template-info.svg 模块文档  [查看] [编辑] [历史] [刷新]

这个模块是LoveLive!系列的一个基础模块,所用数据储存在Module:LoveLive/data。

包装模板

本模块用于以下模板:

详细用法及参数见各模板文档。

非模板函数

角色表述展示表

exprTable和groupExprTable这两个函数只在Template:LoveLive!角色表述/doc#列表中被调用。它们均输出一个表格。

exprTable接受无限个匿名参数,每个参数为一个角色的可用名称(无需指定声优),对于每个参数输出一行表格,展示该角色的所有可用名称和表述实例;如果角色存在多位声优(或演员),输出多行表格,展示指定各个声优时角色的可用名称和表述实例。此外,可传入实名参数|name=note,为值为name的那一行表格的角色条目链接名后添加注释<ref>note</ref>。

{{#invoke:LoveLive|exprTable
|穗乃果|绘里
|-
|雪菜
|-
|玛格丽特
|玛格丽特=薇恩·玛格丽特的名应为“玛格丽特”(参见[[薇恩·玛格丽特#余谈]]),但出于兼容性和稳健性考虑,这里仍将“薇恩”作为可用的名。
}}
人物可以使用的名称显示文字
全名名简称日语全名日语名英语名
高坂穗乃果高坂穗乃果穗乃果果高坂穂乃果穂乃果honoka高坂穂乃果(新田恵海)
绚濑绘里绚濑绘里绘里绘
繪
絵
絢瀬絵里絵里eli絢瀬絵里(南條愛乃)
优木雪菜优木雪菜雪菜雪
菜
優木せつ菜せつ菜setsuna優木せつ菜(楠木ともり)
优木雪菜2雪菜2雪2
菜2
優木せつ菜2せつ菜2setsuna2優木せつ菜(林鼓子)
薇恩·玛格丽特[1]薇恩·玛格丽特
葳恩·玛格丽特
玛格丽特
薇恩
葳恩
ウィーン・マルガレーテマルガレーテ
ウィーン
margarete
wien
ウィーン・マルガレーテ(結那)

groupExprTable接受无限个匿名参数,每个参数为一个组合的可用名称(如有多组成员列表,需指定),对于每个参数输出一行表格,展示该组合的所有可用名称和指定的成员列表。此外,可传入实名参数|name=note,为值为name的那一行表格的组合条目链接后添加说明(note)。

{{#invoke:LoveLive|groupExprTable
|μ's|A-RISE
|-
|虹咲学园学园偶像同好会|虹咲学园学园偶像同好会=9人
|虹咲学园学园偶像同好会132|虹咲学园学园偶像同好会132=13人,2023年4月1日后
}}
组合可以使用的名称成员
μ'sμ'sμ’s缪斯繆斯謬斯缪繆謬lovelive!lovelive穗乃果 绘里 小鸟 海未 凛 真姬 希 花阳 妮可
A-RISEa-risearise翼 英玲奈 杏树
虹咲学园学园偶像同好会(9人)虹咲学园学园偶像同好会虹咲學園學園偶像同好會虹ヶ咲学園スクールアイドル同好会虹ヶ咲ニジガク虹咲虹团虹團虹学会虹學會虹nijigaku步梦 霞 雫 果林 爱 彼方 雪菜楠木灯 艾玛 璃奈
虹咲学园学园偶像同好会(13人,2023年4月1日后)虹咲学园学园偶像同好会132虹咲學園學園偶像同好會132虹ヶ咲学園スクールアイドル同好会132虹ヶ咲132ニジガク132虹咲132虹团132虹團132虹学会132虹學會132虹132nijigaku132侑 步梦 霞 雫 果林 爱 彼方 雪菜林鼓子 艾玛 璃奈 栞子 米娅 岚珠

如果匿名参数的值为-,两个函数均会输出一行空白表格作为分隔线。

两个函数均支持class实名参数,作用是给表格增加class。

{{#invoke:LoveLive|exprTable|class=mw-collapsible mw-collapsed
|瑠璃香
}}
人物可以使用的名称显示文字
全名名简称日语全名日语名英语名
椿瑠璃香椿瑠璃香[堀内麻里菜]
椿瑠璃香[2022]
椿瑠璃香[2023]
椿瑠璃香[2024-1]
椿瑠璃香[2024-2]
椿瑠璃香[2025-tokyo-1]
椿瑠璃香[2025-tokyo-2]
椿瑠璃香[2025-osaka]
椿瑠璃香[2026]
瑠璃香[堀内麻里菜]
瑠璃香[2022]
瑠璃香[2023]
瑠璃香[2024-1]
瑠璃香[2024-2]
瑠璃香[2025-tokyo-1]
瑠璃香[2025-tokyo-2]
瑠璃香[2025-osaka]
瑠璃香[2026]
椿ルリカ[堀内まり菜]
椿ルリカ[2022]
椿ルリカ[2023]
椿ルリカ[2024-1]
椿ルリカ[2024-2]
椿ルリカ[2025-tokyo-1]
椿ルリカ[2025-tokyo-2]
椿ルリカ[2025-osaka]
椿ルリカ[2026]
ルリカ[堀内まり菜]
ルリカ[2022]
ルリカ[2023]
ルリカ[2024-1]
ルリカ[2024-2]
ルリカ[2025-tokyo-1]
ルリカ[2025-tokyo-2]
ルリカ[2025-osaka]
ルリカ[2026]
musical-rurika[堀内麻里菜]
musical-rurika[2022]
musical-rurika[2023]
musical-rurika[2024-1]
musical-rurika[2024-2]
musical-rurika[2025-tokyo-1]
musical-rurika[2025-tokyo-2]
musical-rurika[2025-osaka]
musical-rurika[2026]
椿ルリカ(堀内まり菜)
椿瑠璃香[渡边美穗]
椿瑠璃香[tvdrama]
瑠璃香[渡边美穗]
瑠璃香[tvdrama]
椿ルリカ[渡邉美穂]
椿ルリカ[tvdrama]
ルリカ[渡邉美穂]
ルリカ[tvdrama]
musical-rurika[渡边美穗]
musical-rurika[tvdrama]
椿ルリカ(渡邉美穂)

技术备忘

本专题的其他模块:

模板 模块
{{Llsifcardshow}} Module:Llsifcardshow
{{Llsifeventinfo}} Module:Llsifeventinfo
{{Llsifsonglist}} Module:Llsifsonglist
{{Llsifassonginfo}} Module:Llsifassonginfo
{{Llsifascardshow}} Module:Llsifascardshow
{{Llsifasitem}} Module:Llsifasitem
{{Llsifasptadd}} Module:Llsifasptadd

注释

  1. ↑ 薇恩·玛格丽特的名应为“玛格丽特”(参见薇恩·玛格丽特#余谈),但出于兼容性和稳健性考虑,这里仍将“薇恩”作为可用的名。
-- helper functions
local data = mw.loadData('Module:LoveLive/data')

local _enableLog = false
local function _log(...)
    if _enableLog then
        mw.log(mw.ustring.format(...))
    end
end

local function parseName(name)
    name = mw.ustring.lower(name)

    -- "name[label]"
    local index = mw.ustring.match(name, "%[(.-)%]$")
    if index then
        name = mw.ustring.sub(name, 1, -mw.ustring.len(index) - 3)
        return name, index
    end

    -- "name2"
    index = mw.ustring.match(name, "(%d+)$")
    if index then
        name = mw.ustring.sub(name, 1, -mw.ustring.len(index) - 1)
        index = tonumber(index)
        return name, index
    end

    return name, nil
end


local function getChara(charaName)
    local prefix = mw.ustring.match(charaName, '^(%w+)[-_]')
    if prefix == 'yohane' then
        -- for Yohane characters, remove "yohane-" prefix
        charaName = mw.ustring.sub(charaName, 8)
    end
    local key, index = parseName(charaName)
    local id = data.charaMap[key]
    local chara = data.charaData[id]
    _log(
        "getChara: charaName=%s, key=%s, prefix=%s, index=%s, id=%s, chara.fullName=%s, chara.color=%s",
        charaName, key, tostring(prefix), tostring(index), tostring(id), tostring(chara and chara.fullName.zhHans[1]), tostring(chara and chara.color)
    )
    return chara, prefix, index
end

local _musicalSeiyuuIndexMap = {
    [1] = "2022", [2] = "2023", [3] = "2024-1", [4] = "2024-2"
}
local function getCharaSeiyuu(charaName)
    local chara, prefix, index = getChara(charaName)
    local seiyuu = nil
    if chara then
        if chara.id:find("^musical_") then
            -- compatibility for Musical characters
            index = _musicalSeiyuuIndexMap[index] or index
        end
        if chara.seiyuu then
            seiyuu = chara.seiyuu[index or 1] or chara.seiyuu[1]
        end
    else
        return nil, nil, nil
    end
    _log(
        "getCharaSeiyuu: charaName=%s, chara.id=%s, prefix=%s, index=%s, seiyuu.zhHans[1]=%s",
        charaName, tostring(chara and chara.id), tostring(prefix), tostring(index), tostring(seiyuu and seiyuu.zhHans[1])
    )
    return chara, prefix, seiyuu
end


local function getGroup(groupName)
    local key, index = parseName(groupName)
    local id = data.groupMap[key]
    local group = data.groupData[id]
    _log(
        "getGroup: groupName=%s, key=%s, index=%s, id=%s, group.name=%s, group.color=%s",
        groupName, key, tostring(index), tostring(id), tostring(group and group.name), tostring(group and group.color)
    )
    return group, index
end

local function getGroupMembers(groupName)
    local group, index = getGroup(groupName)
    local members = nil
    if group and group.members then
        members = group.members[index or 1] or group.members[1]
    end
    return group, members, index
end


-- main
local getArgs = require('Module:Arguments').getArgs
local p = {}
local function makeInvokeFunc(funcName, options)
    return function(frame)
        local args = getArgs(frame, options)
        _enableLog = args._enableLog
        return p[funcName](frame, args)
    end
end


-- {{LoveLive!角色颜色}}
p.color = makeInvokeFunc('_color', {
    wrappers = { 'Template:LoveLive!角色颜色' }
})
function p._color(frame, args)
    local d = args.d or ';'
    local expr = ''
    for i, name in ipairs(args) do
        if i > 1 then
            expr = expr .. d
        end
        local groupOrChara = getGroup(name)
        if not groupOrChara then
            groupOrChara = getChara(name)
        end
        if groupOrChara then
            expr = expr .. frame:extensionTag('nowiki', groupOrChara.color)
        else
            -- error("Group or character '" .. name .. "' not found.")
        end
    end
    return expr
end


-- {{LoveLive!角色表述}}
local function writeLj(frame, args, text)
    if text then
        return frame:expandTemplate {
            title = 'lj',
            args = {text}
        }
    else
        return ''
    end
end

local function writeColoredlink(frame, args, color, link, display)
    if args.nocolor or not color then
        local linktext = display and (link .. '|' .. display) or link
        return '[[' .. linktext .. ']]'
    else
        return frame:expandTemplate {
            title = 'coloredlink',
            args = display and {color, link, display} or {color, link}
        }
    end
end

local function writeGroupExpr(frame, args, group)
    local link, display = group.name, nil
    if group.link then
        link, display = group.link, group.name
    end
    if not args.zh and group.nameJa then
        display = writeLj(frame, args, group.nameJa)
    end
    return writeColoredlink(frame, args, group.color, link, display)
end

local function getCharaLinkAndDisplay(frame, args, chara, prefix, usefullName, useZh)
    local link = chara.fullName.zhHans[1]
    local display = nil
    if prefix == 'yohane' then
        -- for Yohane characters, display yohaneName
        display = useZh and chara.yohaneName.zhHans[1] or writeLj(frame, args, chara.yohaneName.ja[1])
    elseif not usefullName then
        display = useZh and chara.givenName.zhHans[1] or writeLj(frame, args, chara.givenName.ja[1])
    elseif not useZh then
        display = writeLj(frame, args, chara.fullName.ja[1])
    end
    return link, display
end

p.expr = makeInvokeFunc('_expr', {
    removeBlanks = false,
    wrappers = { 'Template:LoveLive!角色表述' }
})
function p._expr(frame, args)
    local delim = args.d or '<br>'
    local br = tonumber(args.br)
    local pre = args.pre or ''
    local suf = args.suf or ''
    local expr = ''

    local function writeCharaExpr(charaName)
        local chara, prefix, seiyuu = getCharaSeiyuu(charaName)
        if chara then
            local charaLink, charaDisplay = getCharaLinkAndDisplay(frame, args, chara, prefix, true, args.zh)
            local charaExpr = writeColoredlink(frame, args, chara.color, charaLink, charaDisplay) .. '(' .. pre

            local seiyuuLink = seiyuu.zhHans[1]
            local seiyuuDisplay = args.zh and seiyuu.zhHans[1] or seiyuu.ja[1]
            charaExpr = charaExpr .. writeColoredlink(frame, args, chara.color, seiyuuLink, seiyuuDisplay) .. suf .. ')'

            return args.zh and charaExpr or writeLj(frame, args, charaExpr)
        else
            return "<strong class=\"error\">参数错误:未知名称</strong>"
        end
    end

    for i, name in ipairs(args) do
        if name and name ~= '' and not (i == 2 and name == 'color') then  -- compatibility for "{{LoveLive!角色表述|穗乃果|color}}"
            if i > 1 then
                expr = expr .. delim
                if br and (i - 1) % br == 0 then
                    expr = expr .. '<br>'
                end
            end
            local group, members = getGroupMembers(name)
            if group then
                expr = expr .. writeGroupExpr(frame, args, group) .. (args.d and '(' or ':<br>')
                for j, member in ipairs(members) do
                    if j > 1 then
                        expr = expr .. delim
                    end
                    expr = expr .. writeCharaExpr(member)
                end
                if args.d then
                    expr = expr .. ')'
                end
            else
                expr = expr .. writeCharaExpr(name)
            end
        end
    end

    return expr
end


-- {{LoveLive!声优表述}}
p.seiyuuExpr = makeInvokeFunc('_seiyuuExpr', {
    removeBlanks = false,
    wrappers = { 'Template:LoveLive!声优表述' }
})
function p._seiyuuExpr(frame, args)
    local delim = args.d or '<br>'
    local br = tonumber(args.br)
    local pre, suf
    if args.zh then
        pre = args.pre or '饰演'
        suf = args.suf or ''
    else
        pre = args.pre or ''
        suf = args.suf or '役'
    end
    local expr = ''

    local function writeSeiyuuExpr(charaName)
        local chara, prefix, seiyuu = getCharaSeiyuu(charaName)
        if chara then
            local seiyuuLink = seiyuu.zhHans[1]
            local seiyuuDisplay = args.zh and seiyuu.zhHans[1] or seiyuu.ja[1]
            local seiyuuExpr = writeColoredlink(frame, args, chara.color, seiyuuLink, seiyuuDisplay) .. '(' .. pre

            local charaLink, charaDisplay = getCharaLinkAndDisplay(frame, args, chara, prefix, true, args.zh)
            seiyuuExpr = seiyuuExpr .. writeColoredlink(frame, args, chara.color, charaLink, charaDisplay) .. suf .. ')'

            return args.zh and seiyuuExpr or writeLj(frame, args, seiyuuExpr)
        else
            return "<strong class=\"error\">参数错误:未知名称</strong>"
        end
    end

    for i, name in ipairs(args) do
        if name and name ~= '' then
            if i > 1 then
                expr = expr .. delim
                if br and (i - 1) % br == 0 then
                    expr = expr .. '<br>'
                end
            end
            local group, members = getGroupMembers(name)
            if group then
                expr = expr .. writeGroupExpr(frame, args, group) .. (args.d and '(' or ':<br>')
                for j, member in ipairs(members) do
                    if j > 1 then
                        expr = expr .. delim
                    end
                    expr = expr .. writeSeiyuuExpr(member)
                end
                if args.d then
                    expr = expr .. ')'
                end
            else
                expr = expr .. writeSeiyuuExpr(name)
            end
        end
    end

    return expr
end


-- {{LoveLive!角色表述/short}}
p.shortExpr = makeInvokeFunc('_shortExpr', {
    wrappers = { 'Template:LoveLive!角色表述/short' }
})
function p._shortExpr(frame, args)
    local d = args.d or ' '
    local expr = ''

    for i, name in ipairs(args) do
        if i > 1 then
            expr = expr .. d
        end

        -- "'''name'''" -> "name"
        name, count = mw.ustring.gsub(name, "'''(.-)'''", "%1")
        if count > 0 then
            expr = expr .. "'''"
        end

        local group, members = getGroupMembers(name)
        if group then
            expr = expr .. writeGroupExpr(frame, args, group)
        else
            local chara, prefix, seiyuu = getCharaSeiyuu(name)
            local link, display = getCharaLinkAndDisplay(frame, args, chara, prefix, args.full, true)
            expr = expr .. writeColoredlink(frame, args, chara.color, link, display)
        end

        if count > 0 then
            expr = expr .. "'''"
        end
    end

    return expr
end


-- {{LoveLive!角色头像}}
p.icon = makeInvokeFunc('_icon', {
    wrappers = { 'Template:LoveLive!角色头像' }
})
function p._icon(frame, args)
    local function createSpanImageBox(parent, icon, link, rectangle)
        _log("createSpanImageBox: icon=%s, link=%s, rectangle=%s", tostring(icon), link, tostring(rectangle))
        local span = parent:tag('span')
        if icon then
            span:addClass('image-box')
        else
            span:addClass('image-box-empty')
        end
        if rectangle then
            span:addClass('image-box-rectangle')
        end
        if args.size then
            span:css('width', args.size)
            span:css('height', 'auto')
        end
        if icon then
            span:wikitext(mw.ustring.format('[[File:%s|link=%s|%s]]', icon, link, args.size or '80px'))
        else
            span:wikitext(frame:expandTemplate{ title = '0' })
        end
        span:done()
        return span
    end

    local function createDivHover(parent, icon1, icon2, link)
        local divHover = parent:tag('div')
        divHover:addClass('hover-change')
        divHover:addClass('hovers-float')
            local divBefore = divHover:tag('div')
            divBefore:addClass('hover-change-before')
                createSpanImageBox(divBefore, icon1, link)
            divBefore:done()
            local divAfter = divHover:tag('div')
            divAfter:addClass('hover-change-after')
                createSpanImageBox(divAfter, icon2, link)
            divAfter:done()
        divHover:done()
        return divHover
    end

    local ul = mw.html.create('ul')
    ul:addClass('ll-icon')
    for _, name in ipairs(args) do
        local li = ul:tag('li')
        xpcall(function()  -- try
            local chara, prefix = getChara(name)
            local link, display = getCharaLinkAndDisplay(frame, args, chara, prefix, true, true)

            if args.noText then
                li:css('width', args.size or '')
            end
            if args.smallText then
                li:css('font-size', 'small')
            end
            if not args.noline and (
                chara.id == 'yu' or chara.id == 'musical_madoka' or chara.id == 'musical_kyoka'
            ) then
                li:css('border-left', '2px solid #eee')
            end

            local icons = chara.icons or {}
            if chara.iconType == 'hasu' then
                -- for Hasunosora characters, use icon according to grade
                local grade = tonumber(args.grade)
                local theIcon
                for _, icon in ipairs(icons) do
                    theIcon = icon
                    if grade == nil or grade <= icon.grade then
                        -- if no grade or the grade is less than the character's first grade, use her first grade
                        break
                    end
                    -- if the grade is greater than the character's last grade, use her last grade
                end
                createSpanImageBox(li, theIcon.file, link, true)  -- show rectangle icons
            elseif prefix == 'yohane' then
                -- for Yohane characters, use yohaneIcons instead of icons
                createDivHover(li, chara.yohaneIcons[1], chara.yohaneIcons[2], link)
            elseif not icons[2] then  -- the tables returned by mw.loadData() don't support #table, we must use this way
                createSpanImageBox(li, icons[1], link)  -- icons[1] is nil if no icons
            else
                createDivHover(li, icons[1], icons[2], link)
            end

            if not args.noText then
                local divText = li:tag('div')
                divText:wikitext(writeColoredlink(frame, args, chara.color, link, display))
                if not chara.id:find('^musical_') then
                    -- for Musical characters, don't show seiyuus
                    divText:tag('br')
                    if chara.id == 'setsuna' then
                        -- for characters whose seiyuu changed, show both
                        divText:wikitext('([[' .. chara.seiyuu[1].zhHans[1] .. ']]→')
                        divText:tag('br')
                        divText:wikitext(' [[' .. chara.seiyuu[2].zhHans[1] .. ']])')
                    else
                        divText:wikitext('([[' .. chara.seiyuu[1].zhHans[1] .. ']])')
                    end
                end
                divText:done()
            end

            li:done()
        end, function(message)  -- catch
            li:addClass('ll-icon-error')
            li:wikitext(message)
            li:done()
        end)
    end
    ul:done()
    return tostring(ul)
end


-- {{LoveLive!角色表述/doc}}
local _noteTAEscapeList = {
    emma = {
        {'维尔德', '维-{}-尔德'},
        {'薇蒂', '薇-{}-蒂'}
    },
    mia = {
        {'米娅', '米-{}-娅'},
        {'米雅', '米-{}-雅'}
    },
    margarete = {
        {'薇恩', '薇-{}-恩'},
        {'葳恩', '葳-{}-恩'}
    }
}

p.exprTable = makeInvokeFunc('_exprTable')
function p._exprTable(frame, args)
    local tbl = mw.html.create('table'):addClass('wikitable')
    if args.class then
        tbl:addClass(args.class)
    end
    local tr = tbl:tag('tr')
    tr:tag('th'):attr('rowspan', 2):wikitext('人物'):done()
    tr:tag('th'):attr('colspan', 6):wikitext('可以使用的名称'):done()
    tr:tag('th'):attr('rowspan', 2):wikitext('显示文字'):done()
    tr:done()
    tr = tbl:tag('tr')
    tr:tag('th'):wikitext('全名'):done()
    tr:tag('th'):wikitext('名'):done()
    tr:tag('th'):wikitext('简称'):done()
    tr:tag('th'):wikitext('日语全名'):done()
    tr:tag('th'):wikitext('日语名'):done()
    tr:tag('th'):wikitext('英语名'):done()
    tr:done()

    for _, charaName in ipairs(args) do
        if charaName == '-' then
            tr = tbl:tag('tr')
            tr:tag('th'):attr('colspan', 8):done()
            tr:done()
        else
            local chara, prefix = getChara(charaName)
            tr = tbl:tag('tr')

            seiyuuCount = 0
            for _, _ in ipairs(chara.seiyuu) do
                seiyuuCount = seiyuuCount + 1
            end
            local td = tr:tag('td')
            if seiyuuCount > 1 then
                td:attr('rowspan', seiyuuCount)
            end
            local charaLink = '[[' .. chara.fullName.zhHans[1]
            if prefix == 'yohane' then
                charaLink = charaLink .. '|' .. chara.yohaneName.zhHans[1]
            end
            charaLink = charaLink .. ']]'
            if args[charaName] then
                charaLink = charaLink .. frame:extensionTag('ref', args[charaName])
            end
            td:wikitext(charaLink):done()

            for i, seiyuu in ipairs(chara.seiyuu) do
                if i > 1 then
                    tr = tbl:tag('tr')
                end

                local function getNameExpr(names, seiyuuNames, func)
                    local exprs = {}
                    local function addExpr(name)
                        name = mw.ustring.lower(name)
                        if func then
                            name = func(name)
                        end
                        table.insert(exprs, name)
                    end

                    for j, name in ipairs(names or {}) do
                        if prefix == 'yohane' then
                            name = 'yohane-' .. name
                        end
                        if chara.id:find('^musical_') then
                            for _, label in ipairs(seiyuuNames or {}) do
                                addExpr(name .. '[' .. label .. ']')
                            end
                            for _, label in ipairs(seiyuu.labels or {}) do
                                addExpr(name .. '[' .. label .. ']')
                            end
                        else
                            local escapeList = _noteTAEscapeList[chara.id]
                            if escapeList then
                                for _, pair in ipairs(escapeList) do
                                    name = mw.ustring.gsub(name, pair[1], pair[2])
                                end
                            end
                            addExpr(name .. (i == 1 and '' or i))
                        end
                    end
                    return table.concat(exprs, '<br>')
                end

                if prefix == 'yohane' or chara.id =='lailaps' then
                    tr:tag('td'):attr('colspan', 3):wikitext(getNameExpr(chara.yohaneName.zhHans, seiyuu.zhHans)):done()
                    tr:tag('td'):attr('colspan', 2):wikitext(getNameExpr(chara.yohaneName.ja, seiyuu.ja, function(name) return writeLj(frame, args, name) end)):done()
                    tr:tag('td'):wikitext(getNameExpr(chara.yohaneName.en, seiyuu.zhHans)):done()
                else
                    tr:tag('td'):wikitext(getNameExpr(chara.fullName.zhHans, seiyuu.zhHans)):done()
                    tr:tag('td'):wikitext(getNameExpr(chara.givenName.zhHans, seiyuu.zhHans, function(name) return chara.id == 'musical_sayaka' and 'musical-' .. name or name end)):done()
                    tr:tag('td'):wikitext(getNameExpr(chara.abbr, seiyuu.zhHans, function(name) return '-{' .. name .. '}-' end)):done()
                    tr:tag('td'):wikitext(getNameExpr(chara.fullName.ja, seiyuu.ja, function(name) return writeLj(frame, args, name) end)):done()
                    tr:tag('td'):wikitext(getNameExpr(chara.givenName.ja, seiyuu.ja, function(name) return writeLj(frame, args, name) end)):done()
                    tr:tag('td'):wikitext(getNameExpr(chara.givenName.en, seiyuu.zhHans, function(name) return mw.ustring.lower(chara.id:find('^musical_') and 'musical-' .. name or name) end)):done()
                end

                local expr = ''
                local name
                if prefix ~= 'yohane' then
                    name = chara.id
                elseif chara.id ~= 'yoshiko' then
                    name = 'yohane-' .. chara.id
                else
                    name = 'yohane-yohane'
                end
                if chara.id:find('^musical_') then
                    name = name .. '[' .. seiyuu.zhHans[1] .. ']'
                elseif i ~= 1 then
                    name = name .. i
                end
                expr = expr .. p._expr(frame, {name})
                tr:tag('td'):wikitext(expr):done()

                tr:done()
            end
        end
    end

    tbl:done()
    return tostring(tbl)
end


p.groupExprTable = makeInvokeFunc('_groupExprTable')
function p._groupExprTable(frame, args)
    local tbl = mw.html.create('table'):addClass('wikitable')
    if args.class then
        tbl:addClass(args.class)
    end
    local tr = tbl:tag('tr')
    tr:tag('th'):wikitext('组合'):done()
    tr:tag('th'):wikitext('可以使用的名称'):done()
    tr:tag('th'):wikitext('成员'):done()
    tr:done()

    for _, groupName in ipairs(args) do
        if groupName == '-' then
            tr = tbl:tag('tr')
            tr:tag('th'):attr('colspan', 3):done()
            tr:done()
        else
            local group, members, index = getGroupMembers(groupName)
            tr = tbl:tag('tr')

            local expr = '[['
            if group.link then
                expr = expr .. group.link .. '|'
            end
            expr = expr .. group.name .. ']]'
            if args[groupName] then
                expr = expr .. '(' .. args[groupName] .. ')'
            end
            tr:tag('td'):wikitext(expr):done()

            expr = ''
            local visited = {}
            for _, name in ipairs(group.alternatives) do
                if index and index ~= 1 then
                    name = name .. index
                end
                if not visited[name] then
                    expr = expr .. '<code>' .. name .. '</code>'
                    visited[name] = true
                end
            end
            tr:tag('td'):wikitext(expr):done()

            expr = ''
            for i, member in ipairs(members) do
                if i > 1 then
                    expr = expr .. ' '
                end
                local chara, prefix, seiyuu = getCharaSeiyuu(member)
                expr = expr .. writeColoredlink(frame, args, chara.color, chara.fullName.zhHans[1], chara.givenName.zhHans[1])
                if chara.id == 'setsuna' then
                    expr = expr .. '<sup>' .. writeColoredlink(frame, args, chara.color, seiyuu.zhHans[1]) .. '</sup>'
                end
            end
            tr:tag('td'):wikitext(expr):done()

            tr:done()
        end
    end

    tbl:done()
    return tostring(tbl)
end


return p