モジュール:Wikidata table

local p = {}
local debugging = false
local sep = ", " -- separator for multiple values of same property, changed from ""
local sep2 = "" -- separator for values of different properties
local maxrefs = 3 -- maximum number of references displayed for each statement
-- Internationalisation
local currentlang = mw.language.getContentLanguage()
local i18n = {
	filespace = "File",
	editonwikidata = "ウィキデータを編集",
	ordinal = {
		1 = "st",
		2 = "nd",
		3 = "rd",
		"default" = "th"
	},
}
local months = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }
local mnths = {}
for idx, val in ipairs(months) do
	mnthsidx = val:sub(1,3)
end
-- makeOrdinal needs to be internationalised along with the above i18n
-- takes cardinal number as a numeric and returns the ordinal as a string
-- we need three exceptions in English for 1st, 2nd, 3rd, 21st, .. 31st, etc.
-------------------------------------------------------------------------------
p.makeOrdinal = function(cardinal)
	local card = tonumber(cardinal)
	if not card then return cardinal end
	local ordsuffix = i18n.ordinal.default
	if card % 10 == 1 then
		ordsuffix = i18n.ordinal1
	elseif card % 10 == 2 then
		ordsuffix = i18n.ordinal2
	elseif card % 10 == 3 then
		ordsuffix = i18n.ordinal3
	end
	-- In English, 1, 21, 31, etc. use 'st', but 11, 111, etc. use 'th'
	-- similarly for 12 and 13, etc.
	if (card % 100 == 11) or (card % 100 == 12) or (card % 100 == 13) then
		ordsuffix = i18n.ordinal.default
	end
	return card .. ordsuffix
end
local unitsymbol = {
	Q218593 = "in",
	Q3710 = "ft",
	Q482798 = "yd",
	Q253276 = "mi",
	Q93318 = "nmi",
	Q11573 = "m",
	Q174728 = "cm",
	Q174789 = "mm",
	Q828224 = "km",
	Q81292 = "acres",
	Q35852 = "ha",
	Q712226 = "km2"
}
-- prefixes for particular qualifiers
local prefix = {
	P580 = "from ",
	P582 = "until ",
}
-- external ids which have a formatter url (P1630)
local formaturl = {
	P3563 = "https://wikidata-externalid-url.toolforge.org/?url=https%3A%2F%2Fmsi.nga.mil%2FqueryResults%3Fpublications%2Fngalol%2Flights-buoys%3Fvolume%3D%251%26featureNumber%3D%252%26includeRemovals%3Dfalse%26output%3Dhtml&exp=(%5Cd%7B3%7D)-(.*)&id=$1", -- NGA number
	P613 = "http://nearby.org.uk/coord.cgi?p=$1", -- OS grid reference
	P373 = "https://commons.wikimedia.org/wiki/Category:$1", -- Commons category
	P11235 = "http://damnet.or.jp/cgi-bin/binranA/enAll.cgi?db4=$1", -- Dams in Japan
	P11627 = "https://www.ibiblio.org/lighthouse/$1.htm", -- Lighthouse Directory
}
-- date format default to dmy
local df = "dmy"
-- fallbacks for common properties:
-- property-to-fallback-from = "property-to-fallback-to"
local fallback = {
	P276 = "P131",
	P571 = "P1619",
	P729 = "P1619",
	P4755 = "P296",
	P18 = "P8592"
}
local statedIDs = {
	Q36578 = {"GND", name=1},
	Q547473 = {"MacTutor", name="id"},
	Q19938912 = {"BNF", name=1},
	Q6973052 = {"National Heritage List for England", name="num"},
	Q19842847 = {"Historic Scotland listed building number", name=1}
}
-- error messages
local function errmsg(txt)
	if debugging then
		return "Error: " .. txt
	else
		return nil
	end
end
-- formats the first character of linked item to uppercase
local function ucf(lnk)
	local tbl = mw.text.split( lnk, "|", true )
	local ret
	if tbl2 then -- piped link
		tbl2 = tbl2:gsub("^(%l)", mw.ustring.upper)
		ret = table.concat(tbl, "|")
	elseif lnk:sub(1,2) == "" then -- unpiped link
		ret = lnk:gsub("^(%%%l)", mw.ustring.upper)
	else -- unlinked
		ret = lnk:gsub("^(%l)", mw.ustring.upper)
	end
	return ret
end
-- entrypoint for invoke
function p.ucf(frame)
	return ucf(frame.args.text or "")
end
-- return a number rounded to a precision
local function decimalprecision(x, prec)
	local s = 1
	if x  1 then prec = 1
	elseif prec "
	-- " " .. i18n.editonwikidata .. ""
	return icon
end
-- takes a statement tuple supplied from Wikidata and returns any references
function p._getrefs(statement, qid)
	if not statement.references then return nil end
	local rtbl = {}
	local frm = mw.getCurrentFrame()
	for idx, ref in ipairs(statement.references) do
		if ref.snaks.P854 then -- reference url
			local url = ref.snaks.P8541.datavalue.value
			if ref.snaks.P1476 then -- title (monolingual text)
				local title
				for idx1, titles in ipairs(ref.snaks.P1476) do
					if titles.datavalue.value.language == currentlang then
						title = titles.datavalue.value.text
						local website = ""
						if ref.snaks.P248 then
							local rqid = ref.snaks.P2481.datavalue.value.id
							local label = mw.wikibase.label(rqid,currentlang)
							if label then
								local sitelink = mw.wikibase.sitelink(rqid)
								if sitelink then
									website = "" .. label .. ""
								else
									website = label
								end
							end
						end
						local citeweb = frm:expandTemplate{ title = "Cite web", args = { url=url, title=title, website=website} }
						local hash = "WD" .. mw.hash.hashValue("crc32", citeweb)
						rtbl#rtbl+1 = frm:callParserFunction{ name = "#tag:ref", args = { citeweb, name=hash } }
						break
					end
				end
			else
				local bnf = url:match("https?://data.bnf.fr/ark:/12148/cb(0-9a-Za-Z0-9+)") or url:match("https?://catalogue.bnf.fr/ark:/12148/cb(0-9a-Za-Z0-9+)")
				if bnf then
					rtbl#rtbl+1 = frm:callParserFunction{ name = "#tag:ref", args = { frm:expandTemplate{title="BNF",args={bnf}}, name="bnf"..bnf} }
				else
					local hash = "WD" .. mw.hash.hashValue("crc32", url)
					rtbl#rtbl+1 = frm:callParserFunction{ name = "#tag:ref", args = { url, name=hash } }
				end
			end
		elseif ref.snaks.P248 then
			local rqid = ref.snaks.P2481.datavalue.value.id
			if statedIDsrqid then
				local template = statedIDsrqid
				local propid = mw.wikibase.getBestStatements(rqid,"P1687")
				propid = propid1.mainsnak.datavalue.value.id
				for num, propdata in ipairs(mw.wikibase.getBestStatements(qid, propid)) do
					local property = propdata.mainsnak.datavalue.value
					local args = {template.name = property}
					if rqid == "Q547473" then
						args.title = mw.wikibase.label(qid)
					end
					local result = frm:expandTemplate{title=template1,args=args}
					rtbl#rtbl+1 = frm:callParserFunction{ name = "#tag:ref", args = { result, name=qid .. propid .. num} }
				end
			else
				local citeq = frm:expandTemplate{ title = "Cite Q", args = { rqid } }
				rtbl#rtbl+1 = frm:callParserFunction{ name = "#tag:ref", args = { citeq, name=rqid } }
			end
		end
		if #rtbl>=maxrefs then break end -- maximum number of references reached
	end
	return table.concat(rtbl)
end
-- takes a qid and attempts to return a linked name for it
-- otherwise an unlinked name; otherwise the qid
function p._getLink(qid)
	local lbl = ""
	local slink = mw.wikibase.sitelink(qid)
	local label = mw.wikibase.getLabel(qid)
	if slink and label then
		if slink:lower() == label:lower() then
			if label:find("^%u") then -- match label's case
				lbl = "" .. slink:gsub("^(%l)", mw.ustring.upper) .. ""
			else
				lbl = "" .. slink:gsub("^(%u)", mw.ustring.lower) .. ""
			end
		else
			lbl = "" .. label .. ""
		end
	elseif slink then
		lbl = "" .. slink .. ""
	elseif label then
		lbl = label
	else
		lbl = qid
	end
	return lbl
end
-- entrypoint for #invoke getLink
function p.getLink(frame)
	local qid = (frame.args.qid or ""):upper()
	if qid == "" then return nil end
	return p._getLink(qid)
end
-- takes a snak for a time property and returns the date
local function _getDate(snak, prec)
	local retval
	retval = mw.wikibase.renderSnak(snak)
	if prec == 7 then -- century
		local num, txt = retval:match("^(%d+)%.(.+)$")
		retval = p.makeOrdinal(num) .. txt
	elseif prec == 10 then
		local m, y, e = retval:match("^(%a+) (%d+)(.*)")
		retval = m:sub(1,3) .. " " .. y .. e
	elseif prec == 11 then
		local d, m, y, e = retval:match("^(%d+) (%a+) (%d+)(.*)")
		retval = d .. " " .. m:sub(1,3) .. " " .. y .. e
	end
	return retval
end
-- takes a qid and a property id and returns the corresponding values or nil
-- maxvals greater than zero sets the maximum number of values to return
-- the string quals contains property ids of qualifiers to be returned ('-' is the separator)
function p._getWD(qid, pid, maxvals, quals, colunit)
	maxvals = maxvals or 0
	local ret = {}
	local sortkey
	for idx, prop in ipairs(mw.wikibase.getBestStatements(qid, pid)) do
		local retval
		if prop.mainsnak.snaktype ~= "value" then
			break
		end
		local dtype = prop.mainsnak.datatype
		local dval = prop.mainsnak.datavalue.value
		if dtype == "wikibase-item" then
			retval = p._getLink(dval.id)
		elseif dtype == "monolingualtext" then
			retval = dval.text
		elseif dtype == "commonsMedia" or dtype == "url" then
			retval = dval
		elseif dtype == "external-id" or dtype == "string" then
			if formaturlpid then
				retval = "" .. mw.ustring.gsub(formaturlpid, "$1", dval) .. " " .. dval .. ""
			else
				retval = dval
			end
		elseif dtype == "time" then
			retval = _getDate(prop.mainsnak, dval.precision)
			if dval.precision == 11 and df == "mdy" then
				local d, m, y, e = retval:match("^(%d+) (%a+) (%d+)(.*)")
				retval = m .. " " .. d .. ", " .. y  .. e
			end
			if not sortkey then
				sortkey = prop.mainsnak.datavalue.value.time
			end
		elseif dtype == "quantity" then
			local amount = tonumber(dval.amount)
			local unit = string.match( dval.unit, "(Q%d+)" )
			if unit then
				if unit == colunit then -- unit matches default for this column, so do not display unit
					retval = amount
				else
					if unitsymbolunit then
						if colunit then -- attempt to convert to default unit
							local unit_label = mw.wikibase.label(unit) or unitsymbolunit or '???'
							local colunit_label = mw.wikibase.label(colunit) or unitsymbolcolunit or '???'
							retval = mw.getCurrentFrame():expandTemplate{ title = "cvt", args = {
								amount,
								unitsymbolunit,
								unitsymbolcolunit,
								disp='number'}
							} .. mw.getCurrentFrame():expandTemplate{
								title="efn",
								args = {
									name = string.format(
										'Converted%s%s',
										unitsymbolunit,
										unitsymbolcolunit
									),
									1 = string.format(
										'Change in units: quantity in %s has been automatically converted to %s for uniformity.',
										unit_label,
										colunit_label
									)
								}
							}
						else -- show unit and default conversion
							retval = mw.getCurrentFrame():expandTemplate{ title = "cvt", args = {amount, unitsymbolunit} }
						end
					else
						retval = amount  .. " " .. mw.wikibase.getLabel(unit)
					end
				end
			else
				retval = amount
			end
		elseif dtype == "globe-coordinate" then
			local lat = decimalprecision(dval.latitude, dval.precision)
			local long = decimalprecision(dval.longitude, dval.precision)
			retval = ""
			.. mw.wikibase.formatValue(prop.mainsnak)
			.. ""
		else
			retval = dval
		end
		-- get references
		retval = retval .. (p._getrefs(prop, qid) or "")
		-- get qualifiers
		if quals and prop.qualifiers and retval then
			local qtbl = {}
			for qpid in quals:gmatch("P%d+") do
				if prop.qualifiersqpid then
					for i, qv in ipairs(prop.qualifiersqpid) do
						if qv.snaktype ~= "value" then break end
						local fqv
						if qv.datatype == "globe-coordinate" then
							fqv = mw.wikibase.formatValue(qv) -- linked
						elseif qv.datatype == "time" then
							fqv = _getDate(qv, qv.datavalue.value.precision)
							if qv.datavalue.value.precision == 11 then -- trim to month
								fqv = fqv:match("%d+ (.+)")
							end
						else
							fqv = mw.wikibase.renderSnak(qv) -- plaintext
						end
						if fqv and fqv ~= "" then
							qtbl#qtbl+1 = (prefixqpid or "") .. fqv
						end
					end
				end
			end
			if #qtbl > 0 then
				retval = retval .. " (" .. table.concat(qtbl, " ") .. ")"
			end
		end
		ret#ret+1 = retval
		if maxvals > 0 and #ret >= maxvals then break end
	end
	if #ret "
	if cellrep1 and qid then
		out = out .. cellrep1 .. createicon(qid) .. ""
	elseif not qid then
		out = out .. (cellrep1 or " ") .. ""
	else
		out = out .. ucf(p._getLink(qid)) .. (celladd1 or "") .. createicon(qid) .. ""
	end
	-- split args.pids at comma separators into sequence of cellpids (each may be like P12+P34/P456-P789)
	local cellpids = mw.text.split(args.pids, ",+")
	for c, val in ipairs(cellpids) do
		cols = cols+1
		if cellrepc+1 then
			out = out ..  .. cellrepc+1 .. 
		elseif not qid then
			out = out .. ""
		elseif val=='SD' then
			local short_description = mw.wikibase.getDescription(qid)
			if short_description then
				short_description = currentlang:ucfirst(short_description)
			else
				--local getShortDescription = require('Module:GetShortDescription' ).main
				--local sdt = getShortDescription{name = mw.wikibase.sitelink(qid)}
				short_description = 
			end
			out = out ..  .. short_description .. 
		else
			-- separate multiple properties in same cell, sep=+
			local ptbl = {} -- sequence of values for one cell
			local sortkeyf
			for propandquals in mw.text.gsplit(val, "+", true) do
				-- for each property, split off property from qualifiers, sep=/
				local pid = mw.text.split(propandquals, "/")1
				local unit = pid:match("%((Q%d+)%)") -- capture unit of quantity if specified
				pid = pid:match("P%d+")
				local quals = mw.text.split(propandquals, "/")2
				if pid == "P18" then -- image
					local img = p._getWD(qid, "P18", 1)
					if not img and fallback"P18" then
						img = p._getWD(qid, fallback"P18", 1)
					end
					if img then
						ptbl#ptbl+1 = "100px"
					end
				else
					local wdval,sortkey = p._getWD(qid, pid, 0, quals, unit)
					if not wdval and fallbackpid then
						wdval,sortkey = p._getWD(qid, fallbackpid, 0, quals, unit)
					end
					if not sortkeyf then
						sortkeyf = sortkey
					end
					ptbl#ptbl+1 = wdval and ucf(wdval)
				end
			end -- of loop through multiple properties in same cell
			if sortkeyf then
					sortkeyf = 'data-sort-value="' .. mw.ustring.sub(sortkeyf,2) .. '"'
				else
					sortkeyf = 
			end
			out = out ..  .. table.concat(ptbl, sep2) .. (celladdc+1 or "") .. 
		end
	end -- of loop through all of the cells in the row
	out = out .. ""
	if summary ~= "" then
		out = out .. "" .. summary .. ""
	end
	return out
end
-- entry point for #invoke makerow
function p.makerow(frame)
	local args = {}
	for key, value in pairs(frame:getParent().args) do
		argskey = value
	end
	for key, value in pairs(frame.args) do
		argskey = value
	end
	local isdoc
	if args.doc and args.doc=="no" then
		isdoc = false
	else
		isdoc = mw.title.getCurrentTitle():inNamespace(10)
	end
	if isdoc then
		args.qid = args.example
		return p.doc(args)
	else
		return p._makerow(args)
	end
end
function p.convert(frame)
	local args = frame.args
	local pargs = frame:getParent().args
	local input = args1 or pargs1
	local template = args.template or pargs.template
	if input == nil then
		return nil
	end
	local resolveEntity = require( "Module:ResolveEntityId" )
	local articlelist = mw.text.split(input,"%*%s*")
	local qidlist = {}
	for i,article in ipairs(articlelist) do
		local rawarticle=string.match(article,'%%(.+)%|') or string.match(article,'%%(.+)%%')
		if rawarticle then
			local qid = resolveEntity._id(rawarticle)
	 		if qid then
				qidlist#qidlist+1 = "qid=" .. qid..""
	 		else
	 			qidlist#qidlist+1 = "c1="..rawarticle..""
	 		end
		end
	end
	return table.concat(qidlist,"\n")
end
function p.convert2(frame)
	local args = frame.args
	local pargs = frame:getParent().args
	local input = args1 or pargs1
	local template = args.template or pargs.template
	if input == nil then
		return nil
	end
	local qidlist = mw.text.split(input,"%*%s*")
	local out = ""
	for i,qid in ipairs(qidlist) do
		qid = mw.text.trim(qid)
		out = out .. "\n"
	end
	return out
end
local function proplink(pid)
	local out
	if pid == "P1" then
		out = "Custom input"
	else
		out = mw.getCurrentFrame():expandTemplate{ title = 'Wikidata entity link', args = {pid} }
	end
	return out
end
function p.doc(args)
	local pids = args.pids
	local article = args.article
	local qid = args.qid
	local documentation = require('Module:Documentation').main
	pids = (pids or ""):upper():gsub("%s", "")
	if pids == "" then return errmsg("Missing PIDs") end
	local pidtable = {}
	for c1,val in ipairs(mw.text.split(pids, ",")) do
		pidtablec1 = {}
		for c2,propandquals in ipairs(mw.text.split(val, "+")) do
			pidtablec1c2 = {}
			pidtablec1c2.property = mw.text.split(propandquals, "/")1
			pidtablec1c2.unit = pidtablec1c2.property:match("%((Q%d+)%)")
			if pidtablec1c2.property~='SD' then
				pidtablec1c2.property = pidtablec1c2.property:match("P%d+")
			end
			local quals = mw.text.split(propandquals, "/")2
			if quals then
				pidtablec1c2.quals = {}
				for qpid in quals:gmatch("P%d+") do
					pidtablec1c2.quals1 = qpid
				end
			end
		end
	end
	local function unit(qid)
		if qid then
			local unit = mw.wikibase.sitelink(qid)
			if unit then
				unit = "" .. unit .. "|" .. unitsymbolqid .. ""
			else
				unit = unitsymbolqid
			end
			return " (in " .. unit .. ")"
		else
			return ""
		end
	end
	local out = "This template will retrieve certain data from Wikidata for use in a table in an article. It is a wrapper template for Module:Wikidata table function makerow, using predefined properties.\n\n"
	if article then
		out = out .. "It was designed for use on " .. article .. " but may also be used on other articles. (Search)\n"
	end
	out = out .. "Usage"
	out = out .. "The data that is displayed in each column is as follows:"
	out = out .. 
	for c = 1,#pidtable+1 do
		out = out .. "Column " .. c .. ""
	end
	out = out .. "Label"
	for c1 = 1,#pidtable do
		out = out .. ""
		for c2 = 1,#pidtablec1 do
			if pidtablec1c2.property=='SD' then
				out = out .. 'Short description'
			else
				out = out .. proplink(pidtablec1c2.property) .. unit(pidtablec1c2.unit)
				if pidtablec1c2.quals then
					out = out .. " qualified by "
					for c3 = 1,#pidtablec1c2.quals do
						out = out .. proplink(pidtablec1c2.qualsc3)
					end
				end
			end
			out = out .. ""
		end
		out = out .. ""
	end
	out = out .. ""
	if qid then
		out = out .. "ExampleThe Wikidata QID for "
		local sitelink = mw.wikibase.getSitelink(qid)
		if sitelink then
			out = out .. "" .. sitelink .. ""
		else
			out = out .. mw.wikibase.getLabel(qid)
		end
		out = out .. " is " .. qid .. ". Typing the following:"
		out = out .. "qid=" .. qid .. "produces"
		out = out .. 'Name'
		for c = 1,#pidtable do
			local proplabel
			if pidtablec1.property == "P1" then
				proplabel = "Column " .. c+1
			elseif pidtablec1.property=='SD' then
				proplabel = 'Short description'
			else
				proplabel = currentlang:ucfirst(mw.wikibase.getLabel(pidtablec1.property)) .. unit(pidtablec1.unit)
			end
			out = out .. "" .. proplabel .. ""
		end
		out = out .. "" .. p._makerow(args) .. ""
	end
	out = out .. "Notes\n"
	out = out .. "* The table caption, column headings, etc. need to be added manually.\n"
	out = out .. "* Please be careful about making changes to this template, as corresponding changes will need to be made to the column headings on "
	if article then
		out = out .. "" .. article .. " and any other "
	else
		out = out .. "any "
	end
	out = out .. "article using it. (Search)\n"
	out = out .. "Overriding columns"
	out = out .. "It is possible to override or append to data values obtained from Wikidata in any column in the table.\n"
	out = out .. "* To completely override column n with some custom content, use parameter cn=\n"
	out = out .. "*: For example, to replace column 5 with the number 1957 use c5=1957\n"
	out = out .. "* To append some custom content to the end of column n use parameter cn+=\n"
	out = out .. "*: For example, to add a footnote to column 6 use c6+=This value is approximate.\n"
	out = out .. "Add named reference"
	out = out .. "If you define references in a list then you can cite one of these by using the parameter refname. This will add a reference to column 1 directly after the label.\n"
	out = out .. 'For example you can define a reference called Smith1 by using refs=\n\n\n'
	out = out .. "Then use refname=Smith1"
	if qid then
		out = out .. "References" .. mw.getCurrentFrame():expandTemplate{title='Reflist'}
	end
	out = out .. "TemplateData"
	local td = '{"params": {"qid": {"label": "QID",	"description": "Enter the Wikidata QID, which is a number beginning with Q. You can also add the name of the item as an HTML comment.","example": "'
	if qid then
		td = td .. qid .. 
	else
		td = td .. 'Q1234567'
	end
	td = td  .. '","type": "string","suggested": true}'
	td = td .. ',"refname": {"label": "Add named reference","description": "Add a named reference defined in a list","type": "string","example": "ref1"}'
	td = td .. ',"c1": {"label": "Override label","description": "Override the label (column 1) with this value","type": "content"}'
	td = td .. ',"c1+": {"label": "Append to label","description": "Append this content to the label (column 1)","type": "content"}'
	for c = 1,#pidtable do
		if pidtablec1.property == "P1" then
			td = td .. ',"c' .. c+1 .. '": {"label": "Define column ' .. c+1 .. '","description": "Define column ' .. c+1 .. ' with this content","type": "content"}'
		elseif pidtablec1.property~='SD' then
			local label = mw.wikibase.getLabel(pidtablec1.property)
			td = td .. ',"c' .. c+1 .. '": {"label": "Override ' .. label .. '","description": "Override the ' .. label .. ' (column ' .. c+1 .. ') with this content","type": "content"}'
			td = td .. ',"c' .. c+1 .. '+": {"label": "Append to ' .. label .. '","description": "Append this content to the ' .. label .. ' (column ' .. c+1 .. ')","type": "content"}'
		end
	end
	td = td .. '},"description": "This template reads some data from Wikidata while also allowing an editor to override or append their own content.","format": "inline"}'
	return documentation{content = out .. mw.getCurrentFrame():extensionTag{ name = 'templatedata', content = td}}
end
return p