モジュールの解説[作成]
local p = {}

--共通変数
local track = {}	--トラックテーブル格納用テーブル
local all = {}	--全○○格納用テーブル
local desc = {nil, nil}	--コメント格納用テーブル
local credits = {}	--クレジット表示可否格納用テーブル
local args = {}	--その他引数格納用テーブル
local col = 0	--列数
local width = {}	--列幅格納用テーブル
local collapsed = false	--折りたたみ可否
local writer, lyrics, music, arranger, extra, length = function() return '' end, function() return '' end, function() return '' end, function() return '' end, function() return '' end, function() return '' end	--基本セル関数
local md = true	--microdata on/off


--[[
引数取得
]]
local function getArgs(frame)
	local t_info, t_num = '', 0
	local func_credits = function(v)
		if v == 'yes' then return true end
		local alias = {'+arrangements', '+a', '+編曲', '・編曲'}
		for _k, _v in ipairs(alias) do
			if v == _v then return _v end
		end
		return false
	end
	
	for k, v in pairs(require('Module:Arguments').getArgs(frame, {parentOnly = true})) do
		if string.match(k, '^%D+%d+$') then
			--各トラック格納
			t_info, t_num = string.match(k, '(%D+)(%d+)')
			if not track[t_num] then track[t_num] = {} end
			track[t_num][1] = tonumber(t_num)		-- トラック番号はkey=1で記録
			track[t_num][t_info] = v	-- トラック情報はkey=引数で記録
		elseif string.match(k, 'all_') then
			--全○○格納
			local pattern = string.gsub(k, 'all_', '')
			if not all[pattern] then all[pattern] = {nil, v} end
		elseif string.match(k, '^%d$') then
			--コメント格納
			desc[k] = string.gsub(v, '^([*#;:])', '\n%1')
		elseif string.match(k, '_credits') then
			--クレジット表示可否格納
			credits[string.gsub(k, '_credits', '')] = func_credits(v)
		else
			-- その他格納
			args[k] = v
		end
	end
	
	--allの整理
	if all then
		local function isSame(a, b)	--リンクを考慮して文字列を比較する
			if a and b then
				return (string.match(a[2], '%[%[.-%|(.-)]]') or string.match(a[2], '%[%[(.-)]]') or a[2]) == (string.match(b[2], '%[%[.-%|(.-)]]') or string.match(b[2], '%[%[(.-)]]') or b[2])
			else
				return false
			end
		end
		
		if not all.writing and isSame(all.lyrics, all.music) then
			all.writing = {nil, all.lyrics[2]}
		end
		if all.writing then
			all.lyrics, all.music = nil, nil
			all.writing[1] = '全作詞・作曲: '
		else
			if all.lyrics then all.lyrics[1] = '全作詞: ' end
			if all.music then all.music[1] = '全作曲: ' end
		end
		if isSame(all.writing, all.arrangements) then
			all.all = {'全作詞・作曲・編曲: ', all.writing[2]}
			all.writing, all.arrangements = nil, nil
		else
			if all.arrangements then all.arrangements[1] = '全編曲: ' end
		end
		if isSame(all.music, all.arrangements) then
			all.producing = {'全作曲・編曲: ', all.music[2]}
			all.music, all.arrangements = nil, nil
		end
	end
	
	credits.extra = args.extra_column
	if credits.writing then
		credits.lyrics, credits.music = false, false
		if credits.writing ~= true then credits.arrangements = false end
	end
	if credits.music and credits.music ~= true then credits.arrangements = false end
	collapsed = (args.collapsed == 'yes')
	md = (args.microdata == 'no') and false or args.id
end


--[[
列数計算
]]
local function calc_columns()
	local col = 2	--#,タイトル
	for k, v in pairs(track) do
		if not v.length then
			credits.length = false
		else
			credits.length = true
			break
		end
	end
	for k, v in pairs(credits) do
		if v then col = col + 1 end
	end
	return col
end


--[[
幅計算
]]
local function calc_width()
	local width = {}
	if not credits.length then col = col + 1 end
	if col < 4 then
		width.title, width.credits = '100%', 'auto'
	elseif col < 5 then
		width.title, width.credits = '60%', '40%'
	elseif col < 6 then
		width.title, width.credits = '40%', '30%'
	elseif col < 7 then
		width.title, width.credits = '40%', '20%'
	else
		width.title, width.credits = '40%', '15%'
	end
	return width
end


--[[
ヘッダ部
]]
local function header()
	--キャプション定義
	local result = (args.headline or collapsed) and '<caption style="text-align:left; padding:0.25em 0.9em; font-weight:bold; line-height:1.4em; white-space:nowrap;">' .. (args.headline or 'トラックリスト') .. '</caption>' or ''
	
	if collapsed then
		--折りたたみ表示において[表示]ボタン行追加
		result = result .. '<tr style="position:absolute; top:0.25em; right:0.9em; speak:none;"><th colspan="' .. col .. '"></th></tr>'
	end
	
	local newall = {}	--全○○表示用テーブル
	if all then	--全○○/コメント表示のとき
		if all.lyrics and not credits.lyrics then newall[1] = all.lyrics[1] .. all.lyrics[2] end
		if all.music and not credits.music then newall[#newall + 1] = all.music[1] .. all.music[2] end
		if all.all and not credits.writing and not credits.lyrics and not credits.music and not credits.arrangements then
			newall[1] = all.all[1] .. all.all[2]
		elseif all.writing and not credits.writing and not credits.lyrics and not credits.music then
			newall[1] = all.writing[1] .. all.writing[2]
		elseif all.producing and not credits.music and not credits.arrangements then
			newall[#newall + 1] = all.producing[1] .. all.producing[2]
		end
		if all.arrangements and not credits.arrangements then newall[#newall + 1] = all.arrangements[1] .. all.arrangements[2] end
	end
	
	if newall[1] or #desc > 0 then
		--全○○/コメント行追加
		result = result .. '<tr><td colspan="' .. col .. '" style="padding-left:0.5em;"><small style="font-size:100%;">' .. (desc[1] or '') .. (newall[1] and (table.concat(newall, '、') .. '。') or '') .. (desc[2] or '') .. '</small></td></tr>'
	end
	
	--見出し行追加(#列・タイトル列追加)
	result = result .. '<tr style="text-align:left; background-color:#eee; font-size:111%;"><th scope="col" style="width:2em; padding-right:0.3em; text-align:right; white-space:nowrap;">#</th><th scope="col" style="width:' .. width.title .. '; padding-left:0.9em; white-space:nowrap;">タイトル</th>'
	
	if credits.writing then
		--作詞・作曲列追加
		result = result .. '<th scope="col" style="width:' .. (args.writing_width or (credits.extra and '30%' or '40%')) .. ';">作詞・作曲' .. (credits.writing == true and '' or '・編曲') .. '</th>'
		--作詞・作曲セル関数再定義
		if md then
			writer = function(x) return '<td itemprop="recordingOf" itemscope itemtype="http://schema.org/MusicComposition"><span itemprop="lyricist composer" itemscope itemtype="http://schema.org/Person"><span itemprop="name">' .. (x.writer or all.writer and all.writer[2] or all.all and all.all[2] or '&nbsp;') .. '</span></span></td>' end
		else
			writer = function(x) return '<td>' .. (x.writer or all.writer and all.writer[2] or all.all and all.all[2] or '&nbsp;') .. '</td>' end
		end
	else
		local disp = function(y) if y then return '' else return 'display:none;' end end
		--作詞列追加
		result = result .. '<th scope="col" style="width:' .. (args.lyrics_width or width.credits) .. ';' .. disp(credits.lyrics) .. '">作詞</th>'
		--作詞セル関数再定義
		if md then
			lyrics = function(x) return '<td itemprop="recordingOf" itemscope itemtype="http://schema.org/MusicComposition" style="' .. disp(credits.lyrics) .. '"><span itemprop="lyricist" itemscope itemtype="http://schema.org/Person"><span itemprop="name">' .. (x.lyrics or all.lyrics and all.lyrics[2] or x.writer or all.writing and all.writing[2] or all.all and all.all[2] or '&nbsp;') .. '</span></span></td>' end
		else
			lyrics = function(x) return '<td style="' .. disp(credits.lyrics) .. '">' .. (x.lyrics or all.lyrics and all.lyrics[2] or x.writer or all.writing and all.writing[2] or all.all and all.all[2]  or '&nbsp;') .. '</td>' end
		end
		--作曲列追加
		result = result .. '<th scope="col" style="width:' .. (args.music_width or width.credits) .. ';' .. disp(credits.music) .. '">作曲' .. (credits.music == true and '' or '・編曲') .. '</th>'
		--作曲セル関数再定義
		if md then
			music = function(x) return '<td itemprop="recordingOf" itemscope itemtype="http://schema.org/MusicComposition" style="' .. disp(credits.music) .. '"><span itemprop="composer" itemscope itemtype="http://schema.org/Person"><span itemprop="name">' .. (x.music or all.music and all.music[2] or x.writer or all.writing and all.writing[2] or all.producing and all.producing[2]  or all.all and all.all[2] or '&nbsp;') .. '</span></span></td>' end
		else
			music = function(x) return '<td style="' .. disp(credits.music) .. '">' .. (x.music or all.music and all.music[2] or x.writer or all.writing and all.writing[2] or all.producing and all.producing[2]  or all.all and all.all[2] or '&nbsp;') .. '</td>' end
		end
	end
	if credits.arrangements then
		--編曲列追加
		result = result .. '<th scope="col" style="width:' .. (args.arrangements_width or width.credits) .. ';">編曲</th>'
		--編曲セル関数再定義
		arranger = function(x) return '<td>' .. (x.arranger or all.arrangements and all.arrangements[2] or all.producing and all.producing[2]  or all.all and all.all[2] or '&nbsp;') .. '</td>' end
	end
	if credits.extra then
		--extra列追加
		result = result .. '<th scope="col" style="width:' .. (args.extra_width or width.credits) .. ';">' .. credits.extra .. '</th>'
		--extraセル関数再定義
		extra = function(x) return '<td>' .. (x.extra or '&nbsp;') .. '</td>' end
	end
	if credits.length then
		--時間列追加
		result = result .. '<th scope="col" style="width:4em; padding-right:0.5em; text-align:right; white-space:nowrap;">時間</th>'
		--時間セル再定義
		if md then
			length = function(x) return '<td style="padding-right:0.5em; text-align:right; white-space:nowrap;">' .. (x.length and ('<time itemprop="duration" datetime="'.. string.gsub(x.length, '^(%d+):(%d+)$', 'PT%1M%2S') ..'">' .. x.length .. '</time>') or '&nbsp;') .. '</td>' end
		else
			length = function(x) return '<td style="padding-right:0.5em; text-align:right; white-space:nowrap;">' .. (x.length and ('<time datetime="'.. string.gsub(x.length, '^(%d+):(%d+)$', 'PT%1M%2S') ..'">' .. x.length .. '</time>') or '&nbsp;') .. '</td>' end
		end
	end
	
	result = result .. '</tr>'
	
	return result
end


--[[
本体部
]]
local function body()
	local result = ''
	local trackA = {}	--ソート用テーブル
	for k, v in pairs(track) do
		trackA[#trackA + 1] = v
	end
	table.sort(trackA,
		function (a, b) return a[1] < b[1] end
	)
	if md then
		for k, v in ipairs(trackA) do
			local color = (v[1] % 2 == 0) and '#f7f7f7' or '#fff'	--偶数/奇数で別色指定
			result = result .. '<tr itemprop="track" itemscope itemtype="http://schema.org/MusicRecording" style="background-color:' .. color .. '; vertical-align:top;"><td style="padding-left:0.5em; text-align:right;"><span itemprop="position">' .. (v['#'] or v[1]) .. '</span>.</td><td>' .. (v.title and ('「<span itemprop="name">' .. v.title .. '</span>」') or 'タイトルなし') .. (v.note and ('<small>(' .. v.note .. ')</small>') or '') .. '</td>' .. writer(v) .. lyrics(v) .. music(v) .. arranger(v) ..  extra(v) .. length(v) .. '</tr>'
		end
	else
		for k, v in ipairs(trackA) do
			local color = (v[1] % 2 == 0) and '#f7f7f7' or '#fff'
			result = result .. '<tr style="background-color:' .. color .. '; vertical-align:top;"><td style="padding-left:0.5em; text-align:right;">' .. (v['#'] or v[1]) .. '.</td><td>' .. (v.title and ('「' .. v.title .. '」') or 'タイトルなし') .. (v.note and ('<small>(' .. v.note .. ')</small>') or '') .. '</td>' .. writer(v) .. lyrics(v) .. music(v) .. arranger(v) ..  extra(v) .. length(v) .. '</tr>'
		end
	end
	return result
end


--[[
フッタ部(合計時間)
]]
local function footer()
	local total = args.total_length
	local hour, min, sec = 0, 0, 0
	if total == 'auto' then
		--合計時間計算
		for k, v in pairs(track) do
			if not v.length then v.length = '0:00' end
			local _min, _sec = string.match(v.length, '(%d+):(%d+)')
			min, sec = min + _min, sec + _sec
		end
		while sec > 59 do
			min = min + 1
			sec = sec - 60
		end
		if sec < 10 then sec = '0' .. sec end
		total = min .. ':' .. sec
	end
	if total and total == string.match(total, '%d*:%d*') then
		--<time>タグ
		hour, min, sec = string.match(total, '(%d-):?(%d+):(%d+)')
		hour = (hour ~= '') and tonumber(hour) or 0
		min = hour * 60 + tonumber(min)
		total = '<time datetime="PT' .. min .. 'M' .. sec .. 'S">'.. min .. ':' .. sec .. '</time>'
		--合計時間行追加
		return '<tr style="text-align:right; font-size:111%;"><th colspan="' .. (col - 1) .. '"><div style="width:7.5em; text-align:left; margin:0 0 0 auto; padding-left:0.5em; white-space:nowrap; font-weight:bold; background-color:#eee; outline:1px solid #eee;">合計時間:</div></th><td style="padding-right:0.5em; background-color:#eee;">'.. total .. '</td></tr>'
	else
		return ''
	end
end


function p.main(frame)
	getArgs(frame)
	col = calc_columns()	--number
	width = calc_width()	--table
	
	--tableタグで囲んで出力
	local table = mw.html.create('table')
	table
		:wikitext(header() .. body() .. footer())	--tableタグ内
		:attr({class = 'tracklist', id = args.id or ''})
		:cssText('display:block; max-width:100%; width:auto; border-spacing:0px; border-collapse:collapse; padding:0.2em; font-size:90%; text-align:left;')
	if collapsed then
		--折りたたみ表示追加指定
		table
			:addClass('mw-collapsible mw-collapsed')
			:cssText('position:relative; overflow:hidden; outline:1px solid #aaa;')
	end
	if md then
		table:attr({itemtype = 'http://schema.org/MusicAlbum'})
	end
	return tostring(table)
end

return p