モジュール:Wikidata2

--script that retrieves basic data stored in Wikidata, for the datamodel, see https://www.mediawiki.org/wiki/Extension:Wikibase_Client/Lua
local p = {}
local linguistic = require('Module:Linguistic')
--local formatDate = require('Module:Complex date') only loaded when needed to save memory in large pages like Wikidata:List of properties/all
local fb = require('Module:Fallback')
local i18nmessages = mw.loadData('Module:i18n/wikidata')
-- Wiki-specific parameters
local defaultlang = mw.getCurrentFrame():preprocess("")
local defaultlink = 'wikidata'
local function i18n(str, lang)
	local message = i18nmessagesstr
	if type(message) == 'string' then
		return message
	end
	return fb._langSwitch(message, lang or defaultlang)
end
local function formatError( key, text )
	return error(i18n(key) .. (text or ))
end
local function addTrackingCat(prop, cat)
	if not prop and not cat then
		return error("no property provided")
	end
	if not cat then
		cat = i18nmessages.trackingcat .. '/' .. string.upper(prop)
	end
	return 
end
local function removeBlanks(args)
	for i, j in pairs(args) do -- does not work ??
		if (j == ) or (j == '-') then argsi = nil end
	end
	return args
end
local function formatTheUnknown() -- voir si on peut accorder/adapter l'usage de "inconnu"
	return i18n('somevalue')
end
local function isSpecial(snak)
	return snak.snaktype ~= 'value'
end
local function sameValue(snak, target)
	return not isSpecial(snak) and p.getRawvalue(snak) == target
end
local function showLang(statement, str) -- TODO (not yet in proper format)
	--adds a lang indication at the start of the string, based on data in statement
	local mainsnak = statement.mainsnak
	if isSpecial(mainsnak) then
		return str
	end
	local langlist = {}
	if mainsnak.datavalue.type == 'monolingualtext' then
		langlist = {mainsnak.datavalue.value.language}
	elseif statement.qualifiers and statement.qualifiers.P407 then
		local convertlangcode = mw.loadData('Module:Dictionary/lang codes')
		for i, j in pairs( statement.qualifiers.P407 ) do
			if not isSpecial(j) then
				local val = convertlangcodej.datavalue.value'numeric-id'
				table.insert(langlist, val)
			end
		end
	end
	if #langlist == 0 then
		return str
	else
		return '('.. table.concat(langlist) .. ')' .. str
	end
end
function p.getEntity( val )
	if type(val) == 'table' then
		return val
	end
	return mw.wikibase.getEntityObject(val)
end
-- DATE FUNCTIONS
local function splitTimestamp(timestamp, calendar)
	local pattern = "(%W)(%d+)%-(%d+)%-(%d+)"
	local era, year, month, day = timestamp:match(pattern)
	if calendar == 'julian' then
	--todo  year, month, day = formatdate.gregorianToJulian( era .. year, month, day )
	end
	return {day = day, month = month, year = year, era = era, timestamp = timestamp, type = 'dateobject'}
end
local function rangeObject(begin, ending)
	local timestamp
	if begin then
		timestamp = begin.timestamp
	elseif ending then
		timestamp = ending.timestamp
	end
	return {begin = begin, ending = ending, timestamp = timestamp, type = 'rangeobject'}
end
local function dateObject(orig, params) -- transforme un snak en un nouvel objet utilisable par Module:Date complexe
	if not params then
		params = {}
	end
	local newobj = splitTimestamp(orig.time, orig.calendar) -- initalise l'object en mettant la valeur des dates
	newobj.precision = params.precision or orig.precision
	newobj.type = 'dateobject'
	return newobj
end
local function formatDatepoint(obj, params) -- TO IMPROVE
	if not obj then
		return nil
	end
	local formatDate = require('Module:Complex date')
	local lang = params.lang or defaultlang
	local precision = math.min(obj.precision, params.precision or 15) -- if we don't want to show the value to its full detail
	if precision >= 11 then
		return formatDate.complex_date{args={date1 = obj.year .. '-' .. obj.month .. '-' .. obj.day, lang= lang}}
	elseif precision == 10 then
		return formatDate.complex_date{args={date1 = obj.year .. '-' .. obj.month, lang= lang}}
	elseif precision == 9 then
		return formatDate.complex_date{args={date1 = tostring(obj.year), lang= lang}}
	elseif precision == 8 then
		return formatDate.complex_date{args={date1 = string.sub(tostring(obj.year), 1, 3) .. '0', lang = lang, precision = 'decade'}}
	elseif precision == 7 then
		return formatDate.complex_date{args={date1 = string.sub(tostring(obj.year + 100), 1, 2), lang = lang, precision = 'century'}}
	end
	return nil
end
local function formatDaterange(obj, params) --TODO
	local begin = formatDatepoint(obj.begin, params) or 
	local ending = formatDatepoint(obj.ending, params) or 
	return begin .. '-' .. ending
end
local function objectToText(obj, params)
	if obj.type == 'dateobject' then
		return formatDatepoint(obj, params)
	elseif obj.type == 'rangeobject' then
		return formatDaterange(obj, params)
	end
	return nil
end
local function tableToText(values, params) -- takes a list of already formatted values and make them a text
	if not values then
		return nil
	end
	return linguistic.conj(values, params.lang or defaultlang, params.conjtype)--linguistic.conj( values, params.lang, params.conjtype )
end
function p.getDate(obj)
--
returns an object containing a timestamp for easy sorting, and other data
	possible types of object:
		dateobject
			{timestamp = string, year = number, month = number, day = number, calendar = string}
		rangeobject
			{timestamp = string, begin = dateobject, ending = dateobject}
--
	if not obj then
		return nil
	end
	if type(obj) == 'string' then
		obj = p.getEntity(obj)
	end
	-- if obj is a statement with date, get it
	if obj.mainsnak and not isSpecial(obj.mainsnak) and obj.mainsnak.datatype == 'time' then
		return dateObject(obj.mainsnak.datavalue.value.time)
	end
	-- else preload relevant data
	local qualifs = obj.qualifiers -- when obj is a statement, look in qualifiers
	local claims = obj.claims -- when obj is an item, look in claims
	local pointprop = {'P585', 'P571'} -- dates corresponding to a punctual fact
	local beginprop = {'P580', 'P569'} -- start date, birth date == start of a date range
	local endingprop = {'P582', 'P570'}
	local function getval(prop)
		local val
		if claims and claimsprop and not isSpecial(claimsprop1.mainsnak) then
			val = claimsprop1.mainsnak.datavalue.value
		elseif qualifs and qualifsprop and not isSpecial(qualifsprop1) then
			val = qualifsprop1.datavalue.value
		end
		if val then
			return dateObject(val)
		end
		return nil
	end
	for i, prop in pairs(pointprop) do
		local val = getval(prop)
		if val then return val end
	end
	--if no date has not been found, look for startdate or enddate
	local begin, ending
	for i, prop in pairs(beginprop) do
		begin = getval(prop)
		if begin then
			break
		end
	end
	for i, prop in pairs(endingprop) do
		ending = getval(prop)
		if ending then
			break
		end
	end
	if begin or ending then
		return rangeObject(begin, ending)
	end
	return nil
end
function p.getFormattedDate(statement, params)
	local datetable = p.getDate(statement)
	if not datetable then
		return nil
	end
	return objectToText(datetable, params)
end
local function hasTargetValue(claim, target)
	if target == nil then
		return true
	end
	return sameValue(claim.mainsnak, target)
end
local function hasRank(claim, target)
	if target == 'valid' then
		return hasRank(claim, 'preferred') or hasRank(claim, 'normal')
	else
		return claim.rank == target
	end
end
local function bestRanked(claims)
	if not claims then
		return nil
	end
	local preferred, normal = {}, {}
	for i, j in pairs(claims) do
		if j.rank == 'preferred' then
			table.insert(preferred, j)
		elseif j.rank == 'normal' then
			table.insert(normal, j)
		end
	end
	if #preferred > 0 then
		return preferred
	else
		return normal
	end
end
local function hasQualifier(claim, qualifier, qualifiervalues)
	if not qualifier then -- si aucun qualificatif est demandé, ça passe
		return true
	end
	qualifier = string.upper(qualifier)
	if not claim.qualifiers or not claim.qualifiersqualifier then
		return false
	end
	if type(qualifiervalues) == 'string' then
		qualifiervalues = mw.text.split(qualifiervalues, ',')
	end
	if (not qualifiervalues) or (qualifiervalues == {}) then
		return true -- si aucune valeur spécifique n'est exigée
	end
	for i, j in pairs(claim.qualifiersqualifier) do
		for k, l in pairs(qualifiervalues) do
			if p.getRawvalue(j) == l then
				return true
			end
		end
	end
	return false
end
local function hasSource(statement, source, sourceproperty)
	if not statement.references then
		return false
	end
	sourceproperty = string.upper(sourceproperty or 'P248')
	local sourcevalue = string.upper(source or )
	for i, ref in pairs(statement.references) do
		for prop, content in pairs(ref.snaks) do
			if prop == sourceproperty then
				if sourcevalue ==  then
					return true
				else
					for j, k in pairs(content) do
						if p.getRawvalue(k) == source then
							return true
						end
					end
				end
			end
		end
	end
	return false
end
local function hasDate(statement)
	if not statement.qualifiers then
		return false
	end
	local dateprops = {'P580', 'P585', 'P582'}
	for i, prop in pairs(dateprops) do
		if statement.qualifiersprop then
			return true
		end
	end
	return false
end
local function isInLanguage(snak, lang) -- ne fonctionne que pour les monolingualtext / étendre aux autres types en utilisant les qualifiers ?
	return not isSpecial(snak) and snak.datavalue.type == 'monolingualtext' and snak.datavalue.value.language == lang
end
local function numval(claims, numval) -- retourn les numval premières valeurs de la table claims
	local numval = tonumber(numval) or 0 -- raise a error if numval is not a positive integer ?
	if #claims (' .. id .. ')'
end
function p.getDatavalue(snak, params)
	if isSpecial(snak) then
		return nil
	end
	if not params then
		params = {}
	end
	local displayformat = params.displayformat
	local datatype = snak.datavalue.type
	local value = snak.datavalue.value
	if datatype == 'wikibase-entityid' then
		if type(displayformat) == 'function' then
			return displayformat(snak, params)
		end
		local prefix = 'Q'
		if snak.datavalue.value"entity-type" == 'property' then
			prefix = 'P'
		end
		local id = prefix .. tostring(value'numeric-id')
		if displayformat == 'raw' then
			return id
		elseif displayformat == 'wikidatastyle' then
			return p.showentity(id, params.lang)
		else
			return p.formatEntity(id, params)
		end
	elseif datatype == 'string' then
		local showntext = params.showntext
		if displayformat == 'weblink' then
			if showntext then
				return  .. value .. ' ' .. showntext .. 
			else
				return value
			end
		end
		if snak.datatype == 'math' and displayformat ~= 'raw' then
			value = mw.getCurrentFrame():extensionTag('math', value)
		end
		if params.urlpattern then
			showntext = mw.text.nowiki(showntext or value)
			value = mw.ustring.gsub(value, '%%', '%%%%') -- escape '%'
			value =  .. mw.ustring.gsub(mw.ustring.gsub(params.urlpattern, '$1', value), ' ', '%%20') .. ' ' .. showntext .. 
		end
		return value
	elseif datatype == 'time' then -- format example: +00000001809-02-12T00:00:00Z
		if displayformat == 'raw' then
			return value.time
		else
			return objectToText(dateObject(value), params)
		end
	elseif datatype == 'globecoordinate' then
		-- retourne une table avec clés latitude, longitude, précision et globe à formater par un autre module (à changer ?)
		if displayformat == 'latitude' then
			return value.latitude
		elseif displayformat == 'longitude' then
			return value.longitude
		elseif displayformat == 'qualifier' then
			local coord = require 'Module:Coordinates'
			value.globe = require('Module:Wikidata/Globes')value.globe
			value.precision = nil
			return coord._coord(value)
		else
			value.globe = require('Module:Wikidata/Globes')value.globe -- transforme l'ID du globe en nom anglais utilisable par geohack
			return value -- note : les coordonnées Wikidata peuvent être utilisée depuis Module:Coordinates. Faut-il aussi autoriser à appeler Module:Coordiantes ici ?
		end
	elseif datatype == 'quantity' then -- todo : gérer les paramètre précision
		if displayformat == 'raw' then
			return tonumber(value.amount)
		else
			local formatNum = require 'Module:Formatnum'
			local number = formatNum.formatNum(value.amount)
			local unit = mw.ustring.match(value.unit, '(Q%d+)')
			if unit then
				number = number .. ' ' .. p.formatEntity(unit, params)
			end
			return number
		end
	elseif datatype == 'monolingualtext' then
		return  .. value.text .. 
	else
		return formatError( 'unknown-datavalue-type', datatype )
	end
end
local function getMultipleClaims(args)
	local newargs = args
	local claims = {}
	for i, j in pairs(args.property) do
		newargs.property = j
		local newclaims = p.getClaims(args)
		if newclaims then
			for k, l in pairs(newclaims) do
				table.insert(claims, l)
			end
		end
	end
	return claims
end
function p.getClaims( args ) -- returns a table of the claims matching some conditions given in args
	args = removeBlanks(args)
	if not args.property then
		return formatError( 'property-param-not-provided' )
	end
	if type(args.property) == 'table' then
		return getMultipleClaims(args)
	end
	--Get entity
	if args.item then -- synonyms
		args.entity = args.item
	end
	local entity = args.entity
	if type(entity) ~= 'table' then
		entity = p.getEntity(entity)
	end
	local property = string.upper(args.property)
	if not entity or not entity.claims or not entity.claimsproperty then
		return nil
	end
	if not args.rank then
		args.rank = 'best'
	end
	local claims = {}
-- ~=  lorsque le paramètre est écrit mais laissé blanc dans une fonction frame
	for i, statement in pairs(entity.claimsproperty) do
		if
			(
			not args.excludespecial
			or
			not (isSpecial(statement.mainsnak))
		)
		and
		(
			not args.targetvalue
			or
			hasTargetValue(statement, args.targetvalue)
		)
		and
		(
			not args.qualifier
			or
			hasQualifier(statement, args.qualifier, args.qualifiervalues or args.qualifiervalue)
		)
		and
		(
			not args.withsource or args.withsource == '-'
			or
			hasSource(statement, args.withsource, args.sourceproperty)
		)
		and
		(
			not args.isinlanguage
			or
			isInLanguage(statement.mainsnak, args.isinlanguage)
		)
		and
		(
			args.rank == 'best' -- rank == best est traité à a fin
			or
			hasRank(statement, args.rank)
		)
		then
			table.insert(claims, statement)
		end
	end
	if #claims == 0 then
		return nil
	end
	if args.rank == 'best' then
		claims = bestRanked(claims)
	end
	if args.sorttype then
		claims = p.sortclaims(claims, args.sorttype)
	end
	if args.numval then
		return numval(claims, args.numval)
	end
	return claims
end
function p.formatClaimList(claims, args)
	if not claims then
		return nil
	end
	for i, j in pairs(claims) do
		claimsi = p.formatStatement(j, args)
	end
	return claims
end
function p.stringTable(args) -- like getClaims, but get a list of string rather than a list of snaks, for easier manipulation
	local claims = p.getClaims(args)
	return p.formatClaimList(claims, args)
end
local function getQualifiers(statement, qualifs, params)
	if not statement.qualifiers then
		return nil
	end
	local vals = {}
	for i, j in pairs(qualifs) do
		j = string.upper(j)
		if statement.qualifiersj then
			local inserted = false
			if statement.qualifiersj1.datatype == 'monolingualtext' then
				local in_preferred_lang
				for _, language in pairs(fb.fblist(params.lang or defaultlang)) do
					for _, snak in pairs(statement.qualifiersj) do
						if isInLanguage(snak, language) then
							in_preferred_lang = snak
							break
						end
					end
					if in_preferred_lang then
						break
					end
				end
				if in_preferred_lang then
					table.insert(vals, in_preferred_lang)
					inserted = true
				end
			end
			if not inserted then
				for _, snak in pairs(statement.qualifiersj) do
					table.insert(vals, snak)
				end
			end
		end
	end
	if #vals == 0 then
		return nil
	end
	return vals
end
function p.getFormattedQualifiers(statement, qualifs, params)
	if not params then params = {} end
	local qualiftable = getQualifiers(statement, qualifs, params)
	if not qualiftable then
		return nil
	end
	for i, j in pairs(qualiftable) do
		local params = params
		if j.datatype == 'globe-coordinate' then
			params.displayformat = 'qualifier'
		end
		qualiftablei = p.formatSnak(j, params)
	end
	return linguistic.conj(qualiftable, params.lang or defaultlang)
end
function p.formatStatement( statement, args )
	if not statement.type or statement.type ~= 'statement' then
		return formatError( 'unknown-claim-type', statement.type )
	end
	if not args then args = {} end
	local lang = args.lang or defaultlang
	local str = p.formatSnak( statement.mainsnak, args )
	if args.showlang == true then
		str = showLang(statement, str)
	end
	local qualifs = args.showqualifiers
	if qualifs then
		if type(qualifs) == 'string' then
			qualifs = mw.text.split(qualifs, ',')
		end
		local foundvalues = p.getFormattedQualifiers(statement, qualifs, args)
		if foundvalues then
			if args.delimiter then
				str = str .. args.delimiter .. foundvalues
			else
				str = str .. linguistic.inparentheses(foundvalues, lang)
			end
		end
	end
	if args.showdate then -- when "showdate and p.chronosort are both set, date retrieval is performed twice
		local timedata = p.getDate(statement)
		if timedata then
			local formatteddate = objectToText(timedata, args)
			formatteddate = linguistic.inparentheses(formatteddate, lang)
			str = str ..  .. formatteddate ..
		end
	end
	if args.showsource and statement.references then
		local cite = require 'Module:Cite'
		local frame = mw.getCurrentFrame()
		local sourcestring = 
		for i, ref in pairs(statement.references) do
			if ref.snaks.P248 then
				for j, source in pairs(ref.snaks.P248) do
					if not isSpecial(source) then
						local page
						if ref.snaks.P304 and not isSpecial(ref.snaks.P3041) then
							page = ref.snaks.P3041.datavalue.value
						end
						local s = cite.citeitem('Q' .. source.datavalue.value'numeric-id', lang, page)
						s = frame:extensionTag( 'ref', s )
						sourcestring = sourcestring .. s
					end
				end
			elseif ref.snaks.P854 and not isSpecial(ref.snaks.P8541) then
				s = frame:extensionTag( 'ref', p.getDatavalue(ref.snaks.P8541) )
				sourcestring = sourcestring .. s
			end
		end
		str = str .. sourcestring
	end
	return str
end
function p.formatSnak(snak, params)
	--local params = params or {} pour faciliter l'appel depuis d'autres modules
	if snak.snaktype == 'value' then
		return p.getDatavalue(snak, params)
	elseif snak.snaktype == 'somevalue' then
		return formatTheUnknown()
	elseif snak.snaktype == 'novalue' then
		return i18n('novalue') --todo
	else
		return formatError( 'unknown-snak-type', snak.snaktype )
	end
end
local function defaultLabel(entity, lang, displayformat) -- label when no label is available
	if entity and displayformat == 'id' then
		return entity.id
	end
	return i18n('no-label', lang)
end
function p._getLabel(entity, lang, default)
	if not entity then
		return nil
	end
	if type(entity) ~= 'table' then
		entity = p.getEntity(entity)
	end
	if entity and entity.labels then
		for i, lg in pairs(fb.fblist(lang or defaultlang)) do
			if entity.labelslg then
				return entity.labelslg.value
			end
		end
	end
	return defaultLabel(entity, lang, default)
end
function p._getDescription(entity, lang)
	if not entity then
		return i18n('no description')
	end
	if type(entity) ~= 'table' then
		entity = p.getEntity(entity)
	end
	local descriptions = entity.descriptions
	if not descriptions then
		return i18n('no description')
	end
	if descriptionslang then
		return descriptionslang.value
	end
	local langlist = fb.fblist(lang or defaultlang) -- list of fallback languages if no label in the desired language
	for i, lg in pairs(langlist) do
		if descriptionslg then
			return descriptionslg.value
		end
	end
	return i18n('no description')
end
local function wikipediaLink(entity, lang)
	local link = entity:getSitelink(lang .. 'wiki')
	if link then
		return ':' .. lang .. ':' .. link
	end
	return nil
end
local function getLink(entity, typelink, lang)
	if not typelink or typelink == '-' then
		return nil
	end
	if not lang then
		lang = defaultlang
	end
	if typelink == 'wikidata' then
		if entity.type == 'property' then
			return 'd:P:' .. entity.id
		else
			return 'd:' .. entity.id
		end
	elseif typelink == 'wikipedia' then
		return wikipediaLink(entity, lang)
	elseif typelink == 'anywikipedia' then
		local fallbacklist = fb.fblist(lang)
		for i, lg in pairs(fallbacklist) do
			link = wikipediaLink(entity, lg)
			if link then return link end
		end
	end
	return nil
end
local function formattedLabel(label, entity, args)
	if not args then args = {} end
	local link = getLink(entity, args.link, args.lang)
	if not link then
		link = getLink(entity, defaultlink, args.lang)
	end
	if not link then
		return label
	else
		return  .. label .. 
	end
end
function p.getmainid(claim)
	if claim and not isSpecial(claim.mainsnak) then
		return 'Q' .. claim.mainsnak.datavalue.value'numeric-id'
	end
	return nil
end
function p.formatEntity( entity, args )
	if not entity then
		return nil
	end
	if not args then args = {} end
	if type(entity) == 'string' then
		entity = p.getEntity(entity)
	end
	local label = p._getLabel(entity, args.lang)
	if not label then
		label = entity.id
	end
	return formattedLabel(label, entity, args)
end
function p.getLabel(frame) -- simple for simple templates like }
	local args = frame.args
	local entity = args.entity
	local lang = args.lang
	if lang ==  then
		lang = defaultlang
	end
	if string.sub(entity, 1, 10) == 'Property:P' then
		entity = string.sub(entity, 10)
	elseif (string.sub(entity, 1, 1) ~= 'P' and string.sub(entity, 1, 1) ~= 'Q') or (not tonumber(string.sub(entity, 2))) then
		return i18n('invalid-id')
	end
	if not args.link or args.link ==  then -- by default: no link
		args.link = '-'
	end
	if args.link == '-' then
		return p._getLabel(entity, lang) or i18n('invalid-id')
	else
		return p.formatEntity(entity, args)
	end
end
function p._formatStatements( args )--Format statements and concat them cleanly
	if args.value == '-' then
		return nil
	end
	--If a value is already set, use it
	if args.value and args.value ~=  then
		return args.value
	end
	local valuetable = p.stringTable(args)
	return tableToText(valuetable, args)
end
function p.showQualifier( args )
	local qualifs = args.qualifiers or args.qualifier
	if type(qualifs) == 'string' then
		qualifs = mw.text.split(qualifs, ',')
	end
	if not qualifs then
		return formatError( 'property-param-not-provided' )
	end
	local claims = p.getClaims(args)
	if not claims then
		return nil
	end
	local str = 
	for i, j in pairs(claims) do
		local new = p.getFormattedQualifiers(j, qualifs, args) or 
		str = str .. new
	end
	return str
end
function p._formatAndCat(args)
	local val = p._formatStatements(args)
	if val then
		return val .. addTrackingCat(args.property)
	end
	return nil
end
function p.getTheDate(args)
	local claims = p.getClaims(args)
	if not claims then
		return nil
	end
	local formattedvalues = {}
	for i, j in pairs(claims) do
		table.insert(formattedvalues, p.getFormattedDate(j))
	end
	local val = linguistic.conj(formattedvalues)
	if val and args.addcat == true then
		return val .. addTrackingCat(args.property)
	else
		return val
	end
end
---FONCTIONS depuis le FRAME
function p.getaDate(frame)
	return p.getTheDate(frame.args)
end
function p.getQualifier(frame)
	return p.showQualifier(frame.args)
end
function p.getDescription(frame) -- simple for simple templates like }
	local entity = frame.args.entity
	local lang = frame.args.lang
	return p._getDescription(entity, lang) or i18n('invalid-id')
end
function p.formatStatements( args )
	return p._formatStatements( args )
end
function p.formatStatementsE(frame)
	local args = {}
	if frame == mw.getCurrentFrame() then
		args = frame:getParent().args -- paramètres du modèle appelant (est-ce vraiment une bonne idée ?)
		for k, v in pairs(frame.args) do
			argsk = v
		end
	else
		args = frame
	end
	return p._formatStatements( args )
end
function p.formatAndCat(frame)
	local args = {}
	if frame == mw.getCurrentFrame() then
		args = frame:getParent().args -- paramètres du modèle appelant (est-ce vraiment une bonne idée ?)
		for k, v in pairs(frame.args) do
			argsk = v
		end
	else
		args = frame
	end
	return p._formatAndCat( args )
end
function p.getEntityFromId(id)
	return p.getEntity(id)
end
return p