2023年政策修订增补工作正在进行中,欢迎参与!
  • Moegirl.ICU:萌娘百科流亡社群 581077156(QQ),欢迎对萌娘百科运营感到失望的编辑者加入
  • Moegirl.ICU:账号认领正在试运行,有意者请参照账号认领流程

Module:Sandbox/サンムル/WikiTable

萌娘百科,万物皆可萌的百科全书!转载请标注来源页面的网页链接,并声明引自萌娘百科。内容不可商用。
跳转到导航 跳转到搜索
Template-info.svg 模块文档  [查看] [编辑] [历史] [刷新]
设计参数:
  • ✔已实现arrayName:读取指定的array对象填充表格内容,array对象可能是由{{Array}}创建,也可以是使用[[模块:Var-array]]创建。
  • ✔已实现offsetR:array对象arrayName左上角的单元格在输出表格中的行偏移量。
  • ✔已实现offsetC:array对象arrayName左上角的单元格在输出表格中的列偏移量。
  • ✔已实现t_<property>:设置表格的property属性。
  • ✔已实现r<row>_<property>:设置表格第row行的property属性。
  • ✔已实现r<row>t:设置yesno(默认),指示表格第row行的所有单元格是否均为标题单元格。
  • ✔已实现c<column>_<property>:设置表格第column列的property属性。
  • ✔已实现c<column>t:设置yesno(默认),指示表格第column列的所有单元格是否均为标题单元格。
  • ✔已实现r<row>c<column>:设置表格第row行第column列的内容。
  • ✔已实现r<row>c<column>_<property>:设置表格第row行第column列的property属性。
  • ✔已实现r<row>c<column>t:设置yesno(默认),指示表格第row行第column列的单元格是否为标题单元格。
  • ✖未实现n_<name>:创建一个名为name的匹配规则。可以使用wiki运算式。
    • wiki计算式的结果为空时,表示不符合条件;否则表示符合条件。
  • ✖未实现m_<name>:为名为name的匹配规则编写映射规则。可以使用wiki运算式。返回值必须为以下的格式:
    • <row>:对行的映射。
    • <row>,<column>:对单元格的映射。
  • ✖未实现e_<name>:使用名为name的匹配规则设置所有符合条件的单元格的内容。可以使用wiki运算式。
  • ✖未实现e_<name>_<property>:使用名为name的匹配规则设置所有符合条件的单元格的property属性。可以使用wiki运算式。
  • ✖未实现et_<name>:设置yesno(默认),指示符合名为name的匹配规则的所有单元格是否均为标题单元格。可以使用wiki运算式。

所有标注了“可以使用wiki运算式。”字样的参数,均可以在这个参数的值中书写wiki的计算式,通过%r%c等获取当前匹配中的行和列等数据。

若wiki计算式包含有可能会在作为参数传入前解析的解析器函数、模板、页面引用、标签等wiki文本,请用<nowiki></nowiki>嵌套。

local module = {}

local var_array = require("Module:Var-array")
local luaq = require("Module:Luaq")
local dictionary = require("Module:Dictionary")

function module.create(frame, overrides)
	local args = overrides or frame.args or { }

	local argDic = luaq.asQuery(args)
		:select(function(pair) return pair.key end)
		:groupBy(function(argName)
			if type(argName) == "number" then
				return "raw cell content"
			elseif mw.ustring.match(argName, "^t_") then
				return "table property"
			elseif mw.ustring.match(argName, "^n_") then
				return "named pattern"
			else
				local sub, suffix = mw.ustring.match(argName, "^[rc][1-9]%d*(([t_]?).*)$")
				if #sub == 0 then return "line content"
				elseif sub == "t" then return "line istitle"
				elseif suffix == "_" then return "line property"
				end

				sub, suffix = mw.ustring.match(argName, "^r[1-9]%d*c[1-9]%d*(([t_]?).*)$")
				if #sub == 0 then return "cell content"
				elseif sub == "t" then return "cell istitle"
				elseif suffix == "_" then return "cell property"
				end
			end
			return "others"
		end)
		:where(function(group) return group.key ~= "others" end)
		:toDictionary(
			function(group) return group.key end,
			function(group) return group.query() end
		)
	local keyComparer = function(k1, k2)
			if k1 == k2 then return true --引用相同
			else return (math.max(0, k1.row or 0) == math.max(0, k2.row or 0)) and (math.max(0, k1.col or 0) == math.max(0, k2.col or 0))
			end
		end
	local arrayName = args["arrayName"]
	local array = var_array.get(arrayName) --作为数据源的二维数组
	local offsetR = args["offsetR"] or 0
	local offsetC = args["offsetC"] or 0
	local arrayCellContentDic = luaq.iasQuery(array or luaq.empty())
		:selectMany(
			function(cells) return luaq.iasQuery(cells) end,
			function(cells, cell, rIndex, cIndex) return { position = { row = rIndex, col = cIndex }, content = cell } end
		)
		:toDictionary(
			function(info) return info.position end,
			function(info) return info.content end,
			keyComparer
		)
	local rawCellContentDic = (function()
		local defaultR, defaultC, arrayExists
		if array then defaultR, defaultC, arrayExists = 1, 1, true
		else
			defaultR, defaultC, arrayExists = offsetR, offsetC, false
			array = { }
		end
		if argDic:hasKey("raw cell content") then
			local dic = dictionary.create(keyComparer)
			local row, col = defaultR, defaultC
			local cells = { }
			local group = luaq.iasQuery(argDic.getValue("raw cell content"))
				:foreach(function(item)
					if (item == "|-") then
						row = row + 1
						col = defaultC
						if not arrayExists then table.insert(array, cells) end
						cells = { }
					else
						dic:add({ row = row, col = col }, item)
						col = col + 1
						if not arrayExists then table.insert(cells, item) end
					end
				end)
			if not arrayExists then table.insert(array, cells) end
			return dic
		else return dictionary.create(keyComparer)
		end
	end)()
	local calBoolean = function(text)
		return text ~= nil and mw.ustring.len(text) ~= 0 and text ~= "no"
	end
	local calProperties = function(prefix, func)
		func = func or function(s) return s end
		local globalPropertiesText = func(args[mw.string.format("%s_", prefix)] or "")
		local properties = dictionary.create()
		local iterator = mw.ustring.gmatch(globalPropertiesText, "%s*(%S+)=\"([^\"]*)\"")
		while true do
			local propertyName, propertyValue = iterator()
			if propertyName == nil then break
			else propertyValue = func(propertyValue)
			end
			properties:add(propertyName, mw.ustring.sub(propertyValue, 2, mw.ustring.len(propertyValue) - 1))
		end
		luaq.asQuery(args)
			:select(function(pair)
				local subStr = mw.ustring.format("%s_", prefix)
				local length = mw.ustring.len(subStr)
				if mw.ustring.sub(pair.key, 1, length) == subStr and mw.ustring.len(pair.key) > length then
					return { propertyName = mw.ustring.sub(pair.key, length + 1), propertyValue = func(pair.value) }
				else return nil
				end
			end)
			:where(function(pair) return pair ~= nil end)
			:foreach(function(pair)
				if properties:hasKey(pair.propertyName) then
					properties:setValue(pair.propertyName, pair.propertyValue)
				else
					properties:add(pair.propertyName, pair.propertyValue)
				end
			end)
		return properties
	end
	local specificCellContentDic = (argDic:hasKey("cell content") and {luaq.iasQuery(argDic.getValue("cell content"))} or {luaq.empty})[1]
		:select(function(paramName)
			local row, col = mw.ustring.match(paramName, "^r([1-9]%d*)c([1-9]%d*)$")
			return { position = { row = tonumber(row), col = tonumber(col) }, content = args[paramName] }
		end)
		:toDictionary(
			function(info) return info.position end,
			function(info) return info.content end
		)
	local specificCellIsTitleDic = (argDic:hasKey("cell istitle") and {luaq.iasQuery(argDic.getValue("cell istitle"))} or {luaq.empty})[1]
		:select(function(paramName)
			local row, col = mw.ustring.match(paramName, "^r([1-9]%d*)c([1-9]%d*)t$")
			return { position = { row = tonumber(row), col = tonumber(col) }, istitle = calBoolean(args[paramName]) }
		end)
		:toDictionary(
			function(info) return info.position end,
			function(info) return info.istitle end
		)
	local specificCellPropertiesDic = (argDic:hasKey("cell property") and {luaq.iasQuery(argDic.getValue("cell property"))} or {luaq.empty})[1]
		:select(function(paramName)
			local row, col = mw.ustring.match(paramName, "^r([1-9]%d*)c([1-9]%d*)_")
			return { position = { row = tonumber(row), col = tonumber(col) }, properties = calProperties(mw.ustring.format("r%sc%s", row, col)) }
		end)
		:toDictionary(
			function(info) return info.position end,
			function(info) return info.properties end
		)
	local specificLineContentTable = (argDic:hasKey("line content") and {luaq.iasQuery(argDic.getValue("line content"))} or {luaq.empty})[1]
		:select(function(paramName)
			local lineType, lineIndex = mw.ustring.match(paramName, "^([rc])([1-9]%d*)$")
			return { type = lineType, index = tonumber(lineIndex), content = args[paramName] }
		end)
		:query()
	local specificLineIsTitleTable = (argDic:hasKey("line istitle") and {luaq.iasQuery(argDic.getValue("line istitle"))} or {luaq.empty})[1]
		:select(function(paramName)
			local lineType, lineIndex = mw.ustring.match(paramName, "^([rc])([1-9]%d*)t$")
			return { type = lineType, index = tonumber(lineIndex), istitle = calBoolean(args[paramName]) }
		end)
		:query()
	local specificLinePropertiesTable = (argDic:hasKey("line property") and {luaq.iasQuery(argDic.getValue("line property"))} or {luaq.empty})[1]
		:select(function(paramName)
			local lineType, lineIndex = mw.ustring.match(paramName, "^([rc])([1-9]%d*)_")
			return { type = lineType, index = tonumber(lineIndex), properties = calProperties(mw.ustring.format("%s%s", lineType, lineIndex)) }
		end)
	local tablePropertiesDic = (argDic:hasKey("table property") and {calProperties("t")} or {dictionary.create()})[1]
	local patternDicsTable = (argDic:hasKey("named pattern") and {luaq.iasQuery(argDic.getValue("named pattern"))} or {luaq.empty})[1]
		:select(function(paramName)
			local patternName = mw.ustring.match(paramName, "^n_(.*)$")
			local pattern = args[paramName] or ""
			local mapping = args[mw.ustring.format("m_%s", patternName)] or "%r,%c"
			local gsubFunc = function(expression)
				local wikiText mw.ustring.gsub(expression, "%%[rcexy]", function(capture)
					if capture == "%r" then
						return tostring(info.position.row)
					elseif capture == "%c" then
						return tostring(info.position.col)
					elseif capture == "%e" then
						return mw.text.decode(mw.text.unstripNoWiki(info.content))
					elseif capture == "%x" then
						return tostring(offsetC)
					elseif capture == "%y" then
						return tostring(offsetR)
					end
				end)
				return frame:preprocess(wikiText)
			end
			return luaq.iasQuery(array)
				:selectMany(function(cells, rIndex)
					return luaq.iasQuery(cells)
						:select(function(cell, cIndex)
							return { position = { row = rIndex, col = cIndex }, content = cell }
						end)
				end)
				:where(function(info)
					local patternResult = gsubFunc(pattern)
					return calBoolean(patternResult)
				end)
				:select(function(info)
					local mappingResult = gsubFunc(mapping)
					local row, col = mw.ustring.match(mappingResult, "^(%d*),?(%d*)$")
					if #row == 0 then row = 0 else row = tonumber(row) end
					if #col == 0 then col = 0 else col = tonumber(col) end
					return { mappedPosition = { row = row, col = col }, originalPosition = info.position, content = info.content }
				end)
				:selectMany(
					function(info)
						local t = { }
						local content = args[mw.ustring.format("c_%s", patternName)]
						if content then table.insert(t, { "pattern cell content", mw.text.decode(mw.text.unstripNoWiki(content)) }) end
						local istitle = args[mw.ustring.format("ct_%s", patternName)]
						if istitle then
							local istitleResult = frame:preprocess(mw.text.decode(mw.text.unstripNoWiki(istitle)))
							table.insert(t, { "pattern cell istitle", calBoolean(istitleResult) })
						end
						local properties = calProperties(
							mw.ustring.format("c_%s", patternName),
							gsubFunc
						)
						table.insert(t, { "pattern cell property", properties })
						return luaq.iasQuery(t)
					end,
					function(info, rslt)
						return { position = info.mappedPosition, resultType = rslt[1], resultValue = rslt[2] }
					end
				)
				:groupBy(
					function(info) return info.resultType end,
					function(info) return { position = info.pos, resultValue = info.resultValue } end
				)
				:toDictionary(
					function(group) return group.key end,
					function(group) return group end
				)
		end)
		:query()

	local propertiesToString = function(properties)
		return table.concat(
			luaq.select(properties, function(pair)
				return mw.ustring.format("%s=\"%s\"", pair.key, pair.value)
			end)
			:query(),
			" "
		)
	end

	--输出逻辑
	local tOutput = { }
	table.insert(tOutput, mw.ustring.format("{| %s", propertiesToString(
		luaq.aggregate(
			luaq.iasQuery(patternDicsTable)
				:select(function(dic)
					if dic:hasKey("pattern cell property") then
						return dic:getValue("pattern cell property")
					else return nil
					end
				end)
				:where(function(dic) return dic ~= nil end),
			function(ps1, ps2)
				return luaq.union(ps1, ps2, function(p1, p2) return keyComparer(p1.key, p2.key) end)
			end,
			dictionary.create(nil, tablePropertiesDic)
		)
	)))
	local maxRow = luaq.asQueryFrom(
		luaq.select(rawCellContentDic, function(pair) return pair.key.row or 0 end)
			:defaultIfEmpty(0)
			:max(),
		luaq.select(arrayCellContentDic, function(pair) return pair.key.row or 0 end)
			:defaultIfEmpty(0)
			:max(),
		luaq.iasQuery(patternDicsTable)
			:select(function(dic)
				return luaq.asQueryFrom(
					(dic:hasKey("pattern cell content") and {dic:getValue("pattern cell content")} or luaq.empty)
						:select(function(info) return info.position.row or 0 end)
						:defaultIfEmpty(0)
						:max(),
					(dic:hasKey("pattern cell istitle") and {dic:getValue("pattern cell istitle")} or luaq.empty)
						:select(function(info) return info.position.row or 0 end)
						:defaultIfEmpty(0)
						:max(),
					(dic:hasKey("pattern cell property") and {dic:getValue("pattern cell property")} or luaq.empty)
						:select(function(info) return info.position.row or 0 end)
						:defaultIfEmpty(0)
						:max()
				).max()
			end)
			:defaultIfEmpty(0)
			:max(),
		luaq.select(arrayCellContentDic, function(pair) return pair.key.row or 0 end)
			:defaultIfEmpty(0)
			:max()
	).max()
	for row = 1, maxRow do
		table.add(tOutput, "|-")
		local maxCol = luaq.asQueryFrom(
			luaq.select(colCellDic, function(pair) return pair.key.col or 0 end)
				:defaultIfEmpty(0)
				:max(),
			luaq.select(arrayCellContentDic, function(pair) return pair.key.col or 0 end)
				:defaultIfEmpty(0)
				:max(),
			luaq.iasQuery(patternDicsTable)
				:select(function(dic)
					return luaq.asQueryFrom(
						(dic:hasKey("pattern cell content") and {dic:getValue("pattern cell content")} or luaq.empty)
							:select(function(info) return info.position.col or 0 end)
							:defaultIfEmpty(0)
							:max(),
						(dic:hasKey("pattern cell istitle") and {dic:getValue("pattern cell istitle")} or luaq.empty)
							:select(function(info) return info.position.col or 0 end)
							:defaultIfEmpty(0)
							:max(),
						(dic:hasKey("pattern cell property") and {dic:getValue("pattern cell property")} or luaq.empty)
							:select(function(info) return info.position.col or 0 end)
							:defaultIfEmpty(0)
							:max()
					).max()
				end)
				:defaultIfEmpty(0)
				:max(),
			luaq.select(arrayCellContentDic, function(pair) return pair.key.col or 0 end)
				:defaultIfEmpty(0)
				:max()
		).max()
		for col = 1, maxCol do
			local istitle = (luaq.asQueryFrom(
					luaq.asQuery(specificCellIsTitleDic)
						:where(function(pair) return pair.key.row == row and pair.key.col == col end)
						:select(function(pair) return pair.value end)
						:first(nil),
					luaq.iasQuery(specificLineIsTitleTable)
						:where(function(info)
							if info.type == "r" then return info.index == row
							elseif info.type == "c" then return info.index == col
							end
						end)
						:select(function(info) return info.istitle end)
						:first(nil)
				)
				:concat(luaq.iasQuery(patternDicsTable)
					:where(function(dic) return dic:hasKey("pattern cell istitle") end)
					:selectMany(function(dic) return dic:getValue("pattern cell istitle") end)
					:where(function(info)
						return (info.position.row == 0 or info.position.row == row) or (info.position.col == 0 or info.position.col == col)
					end)
					:select(function(info) return info.resultValue end)
				)
				:where(function(it) return it ~= nil end)
				:first(false)
					and {"!"} or {"|"})[1]
			local properties = luaq.asQuery(specificCellPropertiesDic)
				:where(function(pair) return pair.key.row == row and pair.key.col == col end)
				:select(function(pair) return pair.value end)
				:concat(luaq.iasQuery(specificLinePropertiesTable)
					:where(function(info)
						if info.type == "r" then return info.index == row
						elseif info.type == "c" then return info.index == col
						end
					end)
					:select(function(info) return info.content end)
				)
				:concat(luaq.iasQuery(patternDicsTable)
					:where(function(dic) return dic:hasKey("pattern cell property") end)
					:selectMany(function(dic) return dic:getValue("pattern cell property") end)
					:where(function(info)
						return (info.position.row == 0 or info.position.row == row) or (info.position.col == 0 or info.position.col == col)
					end)
					:select(function(info) return info.resultValue end)
				)
				:aggregate(
					function(ps1, ps2)
						return luaq.union(ps1, ps2, function(p1, p2) return keyComparer(p1.key, p2.key) end)
					end,
					dictionary.create(keyComparer)
				)
			local content = luaq.asQueryFrom(
					luaq.asQuery(specificCellContentDic)
						:where(function(pair) return pair.key.row == row and pair.key.col == col end)
						:select(function(pair) return pair.value end)
						:first(nil),
					luaq.iasQuery(specificLineContentTable)
						:where(function(info)
							if info.type == "r" then return info.index == row
							elseif info.type == "c" then return info.index == col
							end
						end)
						:select(function(info) return info.content end)
						:first(nil)
				)
				:concat(luaq.iasQuery(patternDicsTable)
					:where(function(dic) return dic:hasKey("pattern cell content") end)
					:selectMany(function(dic) return dic:getValue("pattern cell content") end)
					:where(function(info)
						return (info.position.row == 0 or info.position.row == row) or (info.position.col == 0 or info.position.col == col)
					end)
					:select(function(info) return info.resultValue end)
				)
				:concat(luaq.asQueryFrom(
					luaq.asQuery(arrayCellContentDic)
						:where(function(pair) return pair.key.row == row and pair.key.col == col end)
						:select(function(pair) return pair.value end)
						:first(nil),
					luaq.asQuery(rawCellContentDic)
						:where(function(pair) return pair.key.row == row and pair.key.col == col end)
						:select(function(pair) return pair.value end)
						:first(nil)
				))
				:where(function(it) return it ~= nil end)
				:first("")

			if luaq.any(properties) then
				table.insert(mw.ustring.format("%s %s | %s", istitle, propertiesToString(properties), frame:preprocess(content)))
			else
				table.insert(mw.ustring.format("%s %s", istitle, frame:preprocess(content)))
			end
			--输出单元格
			-- [raw cell content] => [array] => [named pattern cell content](<first> => <last>) =>[specific line content] =>[specific cell content]
			-- [named pattern cell istitle/properties](<first> => <last>) => [specific line istitle/properties] => [specific cell istitle/properties]
		end
	end
	table.insert("|}")
	return table.concat(tOutput, "\r\n")
end

return module