Zum Inhalt springen

Modul:SimpleDataAccess

aus Wikipedia, der freien Enzyklopädie
Dies ist eine alte Version dieser Seite, zuletzt bearbeitet am 13. November 2022 um 09:04 Uhr durch Vollbracht (Diskussion | Beiträge). Sie kann sich erheblich von der aktuellen Version unterscheiden.
Vorlagenprogrammierung Diskussionen Lua Unterseiten
Modul Deutsch English

Modul: Dokumentation

Diese Seite enthält Code in der Programmiersprache Lua. Einbindungszahl Cirrus


--[=[ SimpleDataAccess 2022-07-03
Data Management Module for simplified Access from Within Other Modules:
All functions return first values only.

Author: Vollbracht
* qualifyingValue(statement, propQual)		wikiData first qualifier value
* statementQualOrMainSnack(qualifier, property, propQual)	first value
* MainSnackValue(qualifier, property)		wikiData first main snack value
* MainSnackTime(qualifier, property)		wikiData first main snack value
* indirectMSValue(qualifier, properties)	wikiData first main snack value
* qualifiersLineup(qualifier, property)		sequence of wikiData qualifiers
* time	object representing a wikidata time property
			fields:	year, month, day, hour, min, sec,
					precision, timezone, calendarmodel
			constructor:
				time:new(value)			processes snack.datavalue.value e.g.
				time.MainSnack(Qualifier, Property)
										process first claim of property type
			method:
				time:format(fmtStr)		string representation for time object
			function:
				time.lessthan	comparator function for sort e.g.
]=]
	
--Module globals

local _, GeneralTime = pcall(require, "Modul:Wikidata/Time")
local Time = GeneralTime.service

local service = {}

--------------------------- local functions -----------------------------

local get1stStatement = function(qualifier, property, test)
	if qualifier == nil or qualifier == "" then
		mw.log('no qualifier given for statement retrival')
		return ""
	end
	local statementList = {}
	if type(qualifier) == "table" then
		if test then mw.logObject(qualifier) end
		statementList = qualifier:getBestStatements(property)
	else
		if test then mw.log(qualifier) end
		statementList = mw.wikibase.getBestStatements(qualifier, property)
	end
	if test then
		if not statementList or #statementList == 0 then
			mw.logObject(statementList, qualifier .. '.' .. property)
			local all = nil
			if type(qualifier) == "table" then
				all = qualifier:getAllStatements(property)
				mw.logObject(all, 'all of this')
			else
				all = mw.wikibase.getAllStatements(qualifier, property)
				mw.logObject(all, 'all of ' .. qualifier)
			end
			if not all or #all == 0 then
				all = mw.wikibase.getEntity(qualifier)
				mw.logObject(all, 'all of entity: ' .. qualifier)
			end
		else mw.logObject(statementList) end
	end
	if statementList and type(statementList) == "table" then
		local result = statementList[1]
		if type(result) == "table" then
			return result
		end
	end
	mw.log('no data: (' .. qualifier .. '.' .. property .. '):')
	return ""
end


service.time = {
	-- more selective variant to '(.)(%d+)%-(%d+)%-(%d+)T(%d+):(%d+):(%d+)Z':
	MATCHING = '(.)(%d+)%-([0-1]%d)%-([0-3]%d)T([0-2]%d):([0-6]%d):([0-6]%d)Z',
	PRECISIONLEVEL = {
		-- unused:
		'E8', 'E7', 'E6', 'E5', 'E4',
		-- use with caution (only 'year' is in time object) :
		[6]='millennium', [7]='century', [8]='decade', [9]='year',
		-- use at will (fields in time object):
		[10]='month', [11]='day', [12]='hour', [13]='min', [14]='sec'
	},
	DEFAULTFORMAT = {
		[6]='yyyye=( v.Chr.)', [7]='yyyye=( v.Chr.)', [9]='yyyye=( v.Chr.)',
		[9]='yyyye=( v.Chr.)', [10]='mm/yyyy', [11]='dd.mm.yyyy', [12]='HH Uhr',
		[13]='HH:MM Uhr', [14]='E=(+)e=(-)yyyy-mm-ddTHH:MM:SSZ'
	},
	--[[
		lessthan(a, b)
		datetime compare
		parameters:
			a, b	2 service.time objects
		returns:	true	if on lowest precision level a is before b
					false	if necessary properties inavailable or
							if a is same time or later than b on given
							precision level
	]]
	lessthan = function(a, b)
		if not a then return false end
		if not b then return false end
		local precision = 11
		if a.precision then precision = a.precision end
		if b.precision and b.precision < precision then
			precision = b.precision
		end
		if a.era and b.era then
			if a.era ~= b.era then return b.era == '+' end
			if a.year and b.year then
				local na = tonumber(a.year)
				local nb = tonumber(b.year)
				if na ~= nb then return (a.era == '+') == (na < nb) end
			else return false end
		end
		for i = 10, 14 do
			if precision < i then return false end
			local aa = a[service.time.PRECISIONLEVEL[i]]
			local ab = b[service.time.PRECISIONLEVEL[i]]
			if aa and ab then
				local na = tonumber(aa)
				local nb = tonumber(ab)
				if na ~= nb then return na < nb end
			else return false end
			i = i + 1
		end
		return false
	end,
	--[[
		time:format(fmtStr)
		return formated date time value based on given format string
		parameters:
			fmtStr:	limited format string evaluating each appearance of keys:
					either
						%y or %Y for year
						%m for month
						%d for day of month
						%H for 24-hour hour value
						%M for minute
						%S for second
					or
						yy or yyyy for year
						m or mm for month
						d or dd for day of month
						H or HH for 24-hour hour value
						M or MM for minute
						S or SS for second
						e=(<content>)	for content string appearing
										if era is '-' (BC)
						E=(<content>)	for content string appearing
										if era is '+' (CE)
			returns	date time value string
	]]
	format = function(this, fmtStr)
		function d2(value)
			if value > 9 then return value end
			return '0' .. value
		end
		if not fmtStr then
			if this.precision then fmtStr = this.DEFAULTFORMAT[this.precision]
			else return '' end
		end
		if fmtStr:match('%%') then
			if this.year then
				fmtStr = fmtStr:gsub('%%Y', this.year)
				if this.year < 100 then fmtStr = fmtStr:gsub('%%y', this.year)
				else fmtStr = fmtStr:gsub('%%y', d2(this.year % 100)) end
			end
			if this.month then fmtStr = fmtStr:gsub('%%m', d2(this.month)) end
			if this.month then fmtStr = fmtStr:gsub('%%d', d2(this.day)) end
			if this.month then fmtStr = fmtStr:gsub('%%H', d2(this.hour)) end
			if this.month then fmtStr = fmtStr:gsub('%%M', d2(this.min)) end
			if this.month then fmtStr = fmtStr:gsub('%%S', d2(this.sec)) end
		else
			if this.year then
				fmtStr = fmtStr:gsub('yyyy', this.year)
				fmtStr = fmtStr:gsub('jjjj', this.year)
				if this.year < 100 then
					fmtStr = fmtStr:gsub('yy', this.year)
					fmtStr = fmtStr:gsub('jj', this.year)
				else
					fmtStr = fmtStr:gsub('yy', d2(this.year % 100))
					fmtStr = fmtStr:gsub('jj', d2(this.year % 100))
				end
			end
			if this.month then
				fmtStr = fmtStr:gsub('mm', d2(this.month))
				fmtStr = fmtStr:gsub('m', this.month)
			end
			if this.day then
				fmtStr = fmtStr:gsub('dd', d2(this.day))
				fmtStr = fmtStr:gsub('tt', d2(this.day))
				fmtStr = fmtStr:gsub('d', this.day)
				fmtStr = fmtStr:gsub('t', this.day)
			end
			if this.hour then
				fmtStr = fmtStr:gsub('HH', d2(this.hour))
				fmtStr = fmtStr:gsub('H', this.hour)
			end
			if this.min then
				fmtStr = fmtStr:gsub('MM', d2(this.min))
				fmtStr = fmtStr:gsub('M', this.min)
			end
			if this.sec then
				fmtStr = fmtStr:gsub('SS', d2(this.sec))
				fmtStr = fmtStr:gsub('S', this.sec)
			end
			if this.era then
				if this.era == '+' then
					fmtStr = fmtStr:gsub('e=%(.*%)', '')
					fmtStr = fmtStr:gsub('E=%((.*)%)', '%1')
				else
					fmtStr = fmtStr:gsub('E=%(.*%)', '')
					fmtStr = fmtStr:gsub('e=%((.*)%)', '%1')
				end
			end
		end
		return fmtStr
	end,
	--[[
		time.timify(values)
		table of time objects by table of values
		parameters:
			values:	table: {<source>, <source>, ...}
					<source>: (requirements as of time:new() source parameter)
		returns:	table:	{<time object>, <time object>, ...}
							Time objects of <source>-elements that could be
							converted are in this result table. The others are
							just ignored. Hence contains initialized time
							objects only.
					nil if result table empty
	]]
	timify = function(values)
		local result = {}
		for _, v in ipairs(values) do
			local t = service.time:new(v)
			if t and t.year then table.insert(result, t) end
		end
		if #result == 0 then return nil end
		return result
	end
}
--[[
	constructor
	generates a service.time object
	parameters:
		source:	(optional) table of possible time informations as of
				mw.wikibase.getBestStatements(a, b)[1].mainsnak.datavalue.value
				current system time if nil
				NO MANDANTORY INFOS IN TABLE! OBJECT MAY CONTAIN NO TIME DATA!
]]
function service.time:new(source)
	-- time object is UTC system time if no source specified
	if not source or source == '' then
		d = os.date("!*t")
		for k, v in pairs(service.time) do d[k] = v end
		d.precision = 14
		d.timezone = 0
		d.calendarmodel = "http://www.wikidata.org/entity/Q1985727"
		return d
	end
	o = {}
	setmetatable(o, self)
	self.__index = self
	if type(source) == "table" then
		local ts = source["time"]
		if not ts then return o end
		o.era, o.year, o.month, o.day, o.hour, o.min, o.sec
			= ts:match(o.MATCHING)
		o.year = tonumber(o.year)
		o.month = tonumber(o.month)
		o.day = tonumber(o.day)
		o.hour = tonumber(o.hour)
		o.min = tonumber(o.min)
		o.sec = tonumber(o.sec)
		o.precision = source.precision
		o.timezone = source.timezone
		o.calendarmodel = source.calendarmodel
	end
	return o
end

--[[
	snacks(object, <P...>, <all>, <P...>)
	snak values table
	parameters:
		object:	wikidata source:
					qualifier string <Q...> of a wikidata object or
					statement table as of mw.wikibase.getBestStatements or
					
]]
service.snacks = function(object, property, all, qualifier, test)--####
	if not object then return nil end
	if type(object) == 'string' then
		object = object:match('Q%d+')
		if not object then return nil end
		if not property then return nil end
		if all then object = mw.wikibase.getAllStatements(object, property)
		else object = mw.wikibase.getBestStatements(object, property) end
	end
	if not object then return nil end
	if type(object) ~= 'table' then return nil end
	local result = {}
	local result = get1stStatement(qualifier, property, test)
	if result == "" then return nil end
	result = result["mainsnak"]["datavalue"]["value"]
	if type(result) == 'table' then
		return service.time:new(result)
	end
	mw.log('result w/o time object: ' .. result)
	return nil
end


--[[
	WD:qualifiedSnacks(Object, property, {{}})
	limit a list of statements
	parameters:
		target	either a table containing a list of statements
				or an object name (Q1234, e.g.)
		claim	name of a property to get a statement list if target is name
		qualificators	struct of elements by which list of statements may be
						qualified
						currently knowing qualificators.time only:
						if given only those statements are returned that share
						the same time (not before start = P580 e.g.)
]]
service.qualifiedSnacks = function(this, target, claim, qualificators)
	local result = {}
	condInclude = function(statement)
		if not statement then return nil, false end
		if not statement.qualifiers then
			return statement.datavalue.value, false
		end
	end
	if not target then return nil end
	if type(target) == 'string' then
		target = target:match('Q%d+')
		if not target then return nil end
		if not claim then return nil end
		claim = claim:match('P%d+')
		if not pName then return nil end
		target = mw.wikibase.getBestStatements(target, claim)
	end
	if type(target) ~= 'table' then return nil end
	if qualificators then
		if qualificators.time then
			target = Time.filtered(target, qualificators.time)
		end
	else return target end
end


--[[
	getSimple
	text value of a given value
	parameters:
		value as of mainsnak.datavalue.value
	returns:	simple data value:
				value (if no table), value.text, or label of value.id
]]
local function getSimple(value)
	if type(value) == 'table' then
		local id = value["id"]
		if id then
			local label = mw.wikibase.getLabel(id)
			if label then return label
			else
				mw.log('result entity w/o label')
				mw.logObject(mw.wikibase.getEntity(id))
				return id
			end
		end
		local text = value["text"]
		if text and text ~= "" then return text end
		if value["time"] and value["time"] ~= "" then
			local dateTime = service.time:new(value)
			return dateTime:format()
		end
		mw.log('result table w/o id or text')
		mw.logObject(value)
		return ""
	end
	return value
end

--[[
	qualifyingValue(statement, propQual)
	simplified view on data
	Parameters:
		statement:	statement given for an entity
		propQual:	qualifier for a statement value, defaults to MainSnack
	returns: a string value of MainSnack or property qualifier if available
]]
service.qualifyingValue = function(statement, propQual)
	if statement == nil or statement == "" then
		mw.log('no qualifier in pQV')
		return ""
	end
	local result = ""
	if propQual and propQual ~= "" then
		local q = statement["qualifiers"]
	    if q and type(q[propQual]) == "table" then
			-- no fall back to main snak if property value empty!
			return getSimple(q[propQual][1]["datavalue"]["value"])
	    end
		-- no fall back to main snak if property inavailable!
		return ''
	end
	return getSimple(statement["mainsnak"]["datavalue"]["value"])
end

--[[
	statementQualOrMainSnack
	simple value of a statement with priority on qualifying value and fall back
	to main snak value
	parameters:
		qualifier:	string of Q[0-9]+ type for an entity with statements
		property:	string of P[0-9]+ type for usage of first statement
		propQual:	string of P[0-9]+ type for qualifying this statement
	returns:	simple value (no table)
]]
service.statementQualOrMainSnack = function(qualifier, property, propQual, test)
	local statement = get1stStatement(qualifier, property, test)
	if statement == "" then return "" end
	local result = ''
	local q = statement["qualifiers"]
	if q and type(q[propQual]) == "table" then
		result = getSimple(q[propQual][1]["datavalue"]["value"])
	end
	if result == '' then
		return getSimple(statement["mainsnak"]["datavalue"]["value"])
	end
	return result
end

--[[
	MainSnackValue(qualifier, property)
	simplified view on data
	Parameters:
		qualifier:	case 1: wikiData qualifier of an element with a property
					case 2: wikiData element with a property
		property:	Property of the element having result as value
	returns:	a wikiData qualifier or a string value of MainSnack if available
				limited: no regard of anything but first statement
				limited: only label if id
				limited: no regard of text language if text
]]
service.MainSnackValue = function(qualifier, property, test)
	local result = get1stStatement(qualifier, property, test)
	if result == "" then return "" end
	local ms = result["mainsnak"]
	if ms and ms["datavalue"] then
		return getSimple(ms["datavalue"]["value"])
	end
	-- fallback: qualifier P1932 "object named as"
	local q = result["qualifiers"]
	if not q or type(q['P1932']) ~= 'table' then return '' end
	return q['P1932'][1]["datavalue"]["value"]
end

--[[
	indirectMSValue(qualifier, properties)
	Parameters:
		qualifier:	case 1: wikiData qualifier of an element with a property
					case 2: wikiData element with a property
		properties:	sequence of properties in a string, optionally separated
	returns:
		first MainSnackValue of last property of element given in seccond last
		property ... of element given in first property of given element
]]
service.indirectMSValue = function(qualifier, properties)
	if qualifier == nil or qualifier == "" then
		mw.log('no qualifier')
		return ""
	end
	local statementList = {}
	local t = type(qualifier)
	local qual = qualifier
	local result = ""
	mw.log(properties)
	local props = {}
	for prop in properties:gmatch('[pP]%d+') do
		table.insert(props, prop)
	end
	if #props == {} then return "" end
	if t == "table" then
		statementList = qual:getBestStatements(props[1])
	else
		statementList = mw.wikibase.getBestStatements(qual, props[1])
	end
	local i = 1
	-- process all but last properties
	while i < #props do
		if statementList == nil or statementList[1] == nil then
			-- revoke result
			mw.log('no ' .. props[i] .. ' in ' .. qual)
			return ""
		end
		result = statementList[1]["mainsnak"]["datavalue"]["value"]
		t = type(result)
		if t ~= 'table' then
			-- revoke result
			mw.log(result .. ' is ' .. props[1] .. ' in ' .. qual .. ' but ' .. t)
			return ""
		end
		result = result["id"]
		if result == nil or result:find('[qQ]%d+') == nil then
			mw.log(result .. ' is ' .. props[1] .. ' in ' .. qual .. ' but no id.')
			return ""
		end
		qual = result
		mw.log('next for ' .. mw.wikibase.getLabel(qual))
		i = i + 1
		statementList = mw.wikibase.getBestStatements(qual, props[i])
	end
	-- process last property
	if statementList == nil or statementList[1] == nil then
		-- revoke result
		mw.log('no ' .. props[i] .. ' in ' .. qual)
		return ""
	end
	result = statementList[1]["mainsnak"]["datavalue"]["value"]
	if type(result) == 'table' then
		if result["id"] then return mw.wikibase.getLabel(result["id"]) end
		return ""
	end
	return result
end

--[[
	qualifiersLineup(qualifier, property)
	sequence of wikiData qualifiers
	Parameters:
		qualifier:	case 1: wikiData qualifier of an element with a property
					case 2: wikiData element with a property
		property:	Property of the element having result qualifiers as values
	returns:
		best statement's values as long as they are wikiData qualifiers lined up
		in a table with respect on series ordinals if available
	constraint:
		It's in the responsibility of wikiData to provide correct data. In case
		of corruption this function might return an empty table or one that
		apears empty by starting with index ~= 1.
]]
service.qualifiersLineup = function(qualifier, property)
	if qualifier == nil or qualifier == "" then
		return {}
	end
	local statementList = {}
	local t = type(qualifier)
	if t == "table" then
		statementList = qualifier:getBestStatements(property)
	else
		statementList = mw.wikibase.getBestStatements(qualifier, property)
	end
	if statementList == nil then
		return {}
	end
	local result = {}
	for i, elm in ipairs(statementList) do
		local eVal = elm["mainsnak"]["datavalue"]["value"]
		if eVal == nil then return result end
		t = type(eVal)
		if t ~= "table" then return result end
		local eQual = eVal["id"]
		if eQual == nil then return result end
		local iQual = elm["qualifiers"]
		if iQual == nil then
			mw.log('kein qualifier in ' .. eQual)
			table.insert(result, eQual)
		else
			iQual = iQual["P1545"]
			if iQual == nil then
				mw.log('keine Ornungsnummer in ' .. eQual)
				table.insert(result, eQual)
			else
				local eNum = iQual[1]["datavalue"]["value"]
				table.insert(result, eNum, eQual)
			end
		end
	end
	return result
end

service.test = function(frame)
	return service.MainSnackValue('Q115122668', 'P577', 2)
end

return service