Module:entity
From Rhizome Artbase
Documentation for this module may be created at Module:entity/doc
local p = {}
local m = require('Module:manifest')
local template = require('Module:entityTemplate')
local lib = require('Module:entityUtilities')
-- Test in console:
-- =p.render({ ['args'] = {'Q2508'} })
------------------------------------------------
-- dispatch function
function p.render(args)
local arg_entity, d
-- Called from a page as a template (frame) or from another function
-- (regular args)?
if args.args then
arg_entity = lib.entityIdFromFrame(args)
d = tonumber(args.args[2]) or 0
else
arg_entity = args[1]
d = tonumber(args[2]) or 0
end
local entities = {}
-- check if an entity ID, a list of entity IDs,
-- an entity table or a list of entity tables
-- needs to be handled.
-- Final product is a list of entities.
if type(arg_entity) == 'table' then
if table.getn(arg_entity) == 0 then
-- an empty list: nothing to render!
-- return ''
elseif lib.isEntity(arg_entity) then
entities = {arg_entity}
else
for _,v in ipairs(arg_entity) do
if lib.isEntity(v) then
table.insert(entities, v)
elseif mw.wikibase.isValidEntityId(v) then
table.insert(entities, mw.wikibase.getEntity(v))
else
error('Given table should be an entity, list of entities, or list of entity IDs: ' .. mw.dumpObject(v))
end
end
end
elseif mw.wikibase.isValidEntityId(arg_entity) then
table.insert(entities, lib.entity(arg_entity))
else
-- if the item in the array is neither a list of entities nor entity IDs,
-- do nothing for now.
end
-- iterate over entities
local strs_out = {}
for _,entity in ipairs(entities) do
local templateKey = p._findTemplate(entity)
if templateKey then
table.insert(strs_out, p['__' .. templateKey](entity, d))
else
table.insert(strs_out, string.format('No suitable function found to render entity %s. ', tostring(entity.id)))
end
end
local separator = ''
if d > 2 then
separator = ', '
end
return table.concat(strs_out, separator)
end
-- check if there is a custom function to render an item
function p._findTemplate(entity)
entity = lib.entity(entity)
local found_fitting_template = false
for _,statement in pairs( entity:getAllStatements(m.prop.instance_of) ) do
if not found_fitting_template then
local entity_instance_of = statement.mainsnak.datavalue.value.id
for key,qid in pairs(m.item) do
if entity_instance_of == qid and p['__' .. key] ~= nil then
found_fitting_template = key
end
end
end
end
return found_fitting_template
end
--------------------------------------------------------------------------------
-- Simple rendering functions for statements that contain values,
-- and entity properties
function p._instance_of_links(entity)
entity = lib.entity(entity)
local links = {}
local variant_claims = lib.statementList{entity, m.prop.instance_of}
for _,item in ipairs(variant_claims) do
if lib.pageExists(item.id) then
local target_link = mw.title.makeTitle(0, item.id)
table.insert(links, template.render('instance_of_link', {label=item:getLabel(), target=target_link:fullUrl()}))
else
local descr = item:getDescription() or ''
table.insert(links, template.render('instance_of_pill', {label=item:getLabel(), descr=descr}))
end
end
local edit_target_link = mw.title.new('Item:' .. entity.id)
table.insert(links, template.render('edit_link', {target=edit_target_link:fullUrl()}))
return template.render('instance_of_links', {links=table.concat(links, ' ')})
end
function p._aliases(entity)
local aliases = {}
if entity.aliases then
for lang,alias_list in pairs(entity.aliases) do
for _,alias in pairs(alias_list) do
table.insert(aliases, alias.value)
end
end
end
if #aliases == 0 then
return template.HTML.none
else
return table.concat(aliases, ', ')
end
end
function p._linkToEntities(entities)
if type(entities) == 'string' or lib.isEntity(entities) then
entities = {entities}
end
local links = {}
for _,e in ipairs(entities) do
if lib.entityIsUnknown(e) then
table.insert(links, template.HTML.unknown)
elseif lib.entityIsNoValue(e) then
table.insert(links, template.HTML.none)
else
local e = lib.entity(e)
local label = e:getLabel()
local pageLink = e:getSitelink() or e.id
table.insert(links, template.link(label, pageLink))
end
end
return table.concat(links, ', ')
end
function p._url(statements)
if lib.keyInTable(statements, 'mainsnak') then
statements = {statements}
end
local urls = {}
for _,statement in ipairs(statements) do
table.insert(urls, mw.wikibase.formatValue(statement.mainsnak))
end
if #urls == 0 then
return template.HTML.no_data
else
return table.concat(urls, ', ')
end
end
function p._monolingualtext(statements)
if lib.keyInTable(statements, 'mainsnak') then
statements = {statements}
end
local strings = {}
for _,statement in ipairs(statements) do
table.insert(strings, mw.wikibase.formatValue(statement.mainsnak))
end
if #strings == 0 then
return template.HTML.no_data
else
return table.concat(strings, ', ')
end
end
function p._string(statements)
if lib.keyInTable(statements, 'mainsnak') then
statements = {statements}
end
local strings = {}
for _,statement in ipairs(statements) do
table.insert(strings, mw.wikibase.formatValue(statement.mainsnak))
end
if #strings == 0 then
return template.HTML.no_data
else
return table.concat(strings, ', ')
end
end
function p._pointInTime(statements)
if lib.keyInTable(statements, 'mainsnak') then
statements = {statements}
end
local pointsInTime = {}
for _,statement in ipairs(statements) do
table.insert(pointsInTime, mw.wikibase.formatValue(statement.mainsnak))
end
if table.getn(pointsInTime) == 0 then
return template.HTML.no_data
else
return table.concat(pointsInTime, ', ')
end
end
function p._pointInTimeYearOnly(statements)
if lib.keyInTable(statements, 'mainsnak') then
statements = {statements}
end
local pointsInTime = {}
for _,statement in ipairs(statements) do
table.insert(pointsInTime, string.sub(mw.wikibase.renderSnak(statement.mainsnak), -4))
end
if table.getn(pointsInTime) == 0 then
return template.HTML.no_data
else
return table.concat(pointsInTime, ', ')
end
end
function p._legacy_tags(statements)
if lib.keyInTable(statements, 'mainsnak') then
statements = {statements}
end
local legacy_tags = {}
for _,statement in ipairs(statements) do
local data = {}
data.tags = statement.mainsnak.datavalue.value
if statement.qualifiers then
if statement.qualifiers[m.prop.attributed_to] then
for __,qualifier in ipairs(statement.qualifiers[m.prop.attributed_to]) do
data.attributed_to = p.render{qualifier.datavalue.value.id, 2}
end
end
end
table.insert(legacy_tags, template.render('legacy_tags_1', data))
end
local HTML = table.concat(legacy_tags, '')
if #legacy_tags > 0 then
HTML = template.render('legacy_tags', {tags=HTML})
return HTML
end
return ''
end
function p._transcludeDocument(qid)
local page_name = 'Document:'..qid
if lib.pageExists(page_name) then
local page = mw.title.new(page_name)
local page_content = page:getContent()
if lib.isEmpty(page_content) then
return template.HTML.no_data
else
return page_content
end
else
return template.HTML.no_data
end
end
--------------------------------------------------------------------------------
-- rendering a time period for an entity of start and end time (inception/conclusion)
-- are present. otherwise just show inception.
function p._time(entity)
entity = lib.entity(entity)
if entity.claims[m.prop.conclusion] then
local conclusion = p._values(entity:getBestStatements(m.prop.conclusion))
local inception = p._values(entity:getBestStatements(m.prop.inception))
return template.render('active_period', {inception=inception, conclusion=conclusion})
else
return p._data_lines(entity, {m.prop.inception})
end
end
--------------------------------------------------------------------------------
-- Rendering lists of values
function p._values(values)
local results = {}
for _,val in ipairs(values) do
if val.mainsnak.datatype == 'time' then
table.insert(results, p._pointInTime(val))
elseif val.datatype == 'wikibase-item' then
table.insert(results, p._linkToEntities(val))
elseif val.datatype == 'string' then
table.insert(results, p._string(val))
elseif val.mainsnak.datatype == 'url' then
table.insert(results, p._url(val))
elseif val.mainsnak.datatype == 'monolingualtext' then
table.insert(results, p._monolingualtext(val))
end
end
return table.concat(results, ', ')
end
function p._data_lines(entity, properties)
entity = lib.entity(entity)
local data_lines = {}
for _,property in ipairs(properties) do
local statements = lib.statementList{entity, property}
local prop_entity = lib.entity(property)
-- check if the property should be rendered even if it has no data,
-- see manifest's optional properties
local render_property = true
if #statements == 0 then
local instance_of_ids = lib.instances_of(entity)
for _,instance_of_id in ipairs(instance_of_ids) do
if lib.keyInTable(m.prop_optional, instance_of_id) then
for _,prop_optional in ipairs(m.prop_optional[instance_of_id]) do
if prop_optional == property then
render_property = false
end
end
end
end
end
if render_property then
local label = prop_entity:getLabel()
-- if several statements are rendered, check for plural form of
-- the property's label:
if #statements > 1 then
if lib.keyInTable(prop_entity.claims, m.prop.plural_form_of_label) then
label = prop_entity.claims[m.prop.plural_form_of_label][1].mainsnak.datavalue.value.text
end
end
if prop_entity.datatype == 'time' then
table.insert(data_lines, template.render('data_line', {
pid=prop_entity.id,
key=label,
value=p._pointInTime(statements)
}))
elseif prop_entity.datatype == 'wikibase-item' then
local renderedValues = {}
for _,statement in ipairs(statements) do
local referencedEntity = lib.entity(statement)
local templateKey = p._findTemplate(referencedEntity)
if templateKey then
table.insert(renderedValues, p.render{{referencedEntity}, 3})
else
table.insert(renderedValues, p._linkToEntities(statement))
end
end
table.insert(data_lines, template.render('data_line', {
pid=prop_entity.id,
key=label,
value=table.concat(renderedValues, ', ')
}))
elseif prop_entity.datatype == 'string' then
table.insert(data_lines, template.render('data_line', {
pid=prop_entity.id,
key=label,
value=p._string(statements)
}))
elseif prop_entity.datatype == 'url' then
table.insert(data_lines, template.render('data_line', {
pid=prop_entity.id,
key=label,
value=p._url(statements)
}))
elseif prop_entity.datatype == 'monolingualtext' then
table.insert(data_lines, template.render('data_line', {
pid=prop_entity.id,
key=label,
value=p._monolingualtext(statements)
}))
else
table.insert(data_lines, 'No function to render statements of data type ' .. prop_entity.datatype)
end
end
end
return table.concat(data_lines)
end
function p._data_line_nested(entity, nesting_property, collect_property)
local prop_entity = lib.entity(collect_property)
local statements = lib.nestedStatementList(entity, nesting_property, collect_property)
local local_statements = lib.statementList{entity, collect_property}
for _,v in ipairs(local_statements) do
table.insert(statements, v)
end
local values = p._values(statements)
local label = prop_entity:getLabel()
if #statements > 1 then
if lib.keyInTable(prop_entity.claims, m.prop.plural_form_of_label) then
label = prop_entity.claims[m.prop.plural_form_of_label][1].mainsnak.datavalue.value.text
end
end
if #statements > 0 then
return template.render('data_line', {pid=prop_entity.id, key=label, value=values})
else
return ''
end
end
--------------------------------------------------------------------------------
-- Complex sub-components
function p._carousel(entity)
local entity = lib.entity(entity)
local carousel_context = {}
carousel_context.carousel_id = 'Carousel' .. entity.id
local images_strs_out = {}
local images = lib.statementList{entity, m.prop.image}
local active = ' active'
if table.getn(images) > 0 then
for _,image in ipairs(images) do
if image.mainsnak.datatype == 'localMedia' then
local image_context = {}
image_context.active = active
image_context.image_name = image.mainsnak.datavalue.value
image_context.image_page = mw.title.new('File:' .. image_context.image_name):canonicalUrl()
image_context.image_url = mw.title.new('Special:FilePath/' .. image_context.image_name):canonicalUrl()
if image_context.image_url:match"\.gif$" then
table.insert(images_strs_out, template.render('carousel_image_gif', image_context))
else
table.insert(images_strs_out, template.render('carousel_image', image_context))
end
active = ''
end
end
end
if table.getn(images) > 1 then
carousel_context.carousel_controls = template.render('carousel_controls', {carousel_id = carousel_context.carousel_id})
else
carousel_context.carousel_controls = ''
end
carousel_context.carousel_images = table.concat(images_strs_out, '')
return template.render('carousel', carousel_context)
end
--------------------------------------------------------------------------------
-- Artwork item
function p.__artwork(entity, d)
local data = {}
data.label = entity:getLabel()
data.qid = entity.id
if d == 0 then
-- instance of
data.instance_of = p._instance_of_links(entity)
-- credits (artist + production year)
data.credits = p._data_lines(entity, {m.prop.artist})
data.credits = data.credits .. p._time(entity)
-- image carousel
data.carousel = p._carousel(entity)
-- get descriptions (summaries always on top)
local summary = lib.statementList{entity, m.prop.description, filter=m.item.summary, order_by=m.prop.inception, desc=true}
summary = p.render{summary, 1}
local description = lib.statementList{entity, m.prop.description, filter=m.item.description, order_by=m.prop.inception, desc=true}
description = p.render{description, 1}
local artist_statement = lib.statementList{entity, m.prop.description, filter=m.item.artist_statement, order_by=m.prop.inception, desc=true}
artist_statement = p.render{artist_statement, 1}
data.description = summary .. description .. artist_statement
-- get variants
local variants = lib.statementList{entity, m.prop.variant, order_by=m.prop.inception}
data.variant_links = p.render{variants, 2}
-- legacy tags
data.legacy_tags = lib.statementList{entity, m.prop.legacy_tags}
data.legacy_tags = p._legacy_tags(data.legacy_tags)
-- metadata
---- descriptive
data.data_lines = p._data_lines(entity, {m.prop.artist}) ..
template.render('data_line', {key='title', value=data.label}) ..
p._data_lines(entity, {m.prop.inception})
local metadata_descriptive = template.render('metadata_descriptive', data)
--- variant_history
local metadata_variant_history = ''
if #variants > 0 then
local variants_rendered = p.render{variants, 1}
metadata_variant_history = template.render('variant_history', {variants=variants_rendered})
end
--- administrative
local license = p._data_lines(entity, {m.prop.license})
local alternative_titles = p._data_line_nested(entity, m.prop.variant, m.prop.alternative_title)
local administrative_data_lines = alternative_titles .. license
local metadata_administrative = ''
if string.len(administrative_data_lines) > 0 then
metadata_administrative = template.render('metadata_administrative', {data_lines=administrative_data_lines})
end
data.metadata = metadata_descriptive .. metadata_variant_history .. metadata_administrative
-- Set page display title to artwork title
mw.ext.displaytitle.set(data.label)
return template.render('artwork_0', data)
elseif d == 1 then
data.inception = p._pointInTimeYearOnly(entity.claims[m.prop.inception])
local artists = lib.statementList{entity, m.prop.artist}
data.artist = p.render{artists, 4}
data.image_url = 'pikachu.png'
data.artwork_page_url = mw.title.new(entity.id):canonicalUrl()
-- look for preview image and use that if possible, if none exits pick
-- first one from regular images
local images
if entity.claims[m.prop.preview_image] then
images = mw.wikibase.getBestStatements(entity.id, m.prop.preview_image)
else
images = mw.wikibase.getBestStatements(entity.id, m.prop.image)
end
if #images > 0 then
local topimage = images[1]
if topimage.mainsnak.datatype == 'localMedia' then
local image_context = {}
local image_name = topimage.mainsnak.datavalue.value
data.image_url = mw.title.new('Special:Redirect/file/' .. image_name):canonicalUrl()
if not data.image_url:match"\.gif$" then
data.image_url = data.image_url .. '?width=800'
end
end
end
return template.render('artwork_1', data)
elseif d == 2 or d == 3 then
return p._linkToEntities(entity)
elseif d == 4 then
return data.label
end
return string.format('No support for rendering entity of type artwork at distance level %s.', tostring(d))
end
--------------------------------------------------------------------------------
-- Person item
function p.__person(entity, d)
local entity = lib.entity(entity)
local data = {}
data.label = entity:getLabel()
data.qid = entity.id
if d == 0 then
data.instance_of = p._instance_of_links(entity)
data.data_lines = p._data_lines(entity, {m.prop.official_website, m.prop.member_of})
local artworks = lib.statementList{entity, m.prop.artwork, filter=m.item.artwork, order_by=m.prop.inception}
data.artworks = ''
if #artworks > 0 then
data.artworks = template.render('person_0_artworks', {artworks=p.render{artworks, 1}})
end
-- Set page display title to person name
mw.ext.displaytitle.set(data.label)
return template.render('person_0', data)
elseif d == 2 or d == 3 then
return p._linkToEntities(entity)
elseif d == 4 then
return data.label
end
return string.format('No support for rendering entity of type person at distance level %s.', tostring(d))
end
--------------------------------------------------------------------------------
-- collective item
function p.__collective(entity, d)
local entity = lib.entity(entity)
local data = {}
data.label = entity:getLabel()
data.qid = entity.id
if d == 0 then
data.instance_of = p._instance_of_links(entity)
data.data_lines = p._data_lines(entity, {m.prop.has_member, m.prop.official_website})
local artworks = lib.statementList{entity, m.prop.artwork, filter=m.item.artwork, order_by=m.prop.inception}
data.artworks = p.render{artworks, 1}
mw.ext.displaytitle.set(data.label)
return template.render('collective_0', data)
elseif d == 2 or d == 3 then
local members = lib.statementList{entity, m.prop.has_member}
local members_rendered = {}
for _,member in ipairs(members) do
table.insert(members_rendered, p.render{{member}, d+1})
end
data.members = table.concat(members_rendered, ', ')
if #data.members > 0 then
data.members = ' (' .. data.members .. ')'
end
data.collective_link = p._linkToEntities(entity)
return template.render('collective_1', data)
elseif d == 4 then
return data.label
end
return string.format('No support for rendering entity of type collective at distance level %s.', tostring(d))
end
--------------------------------------------------------------------------------
-- Institution item
function p.__institution(entity, d)
local entity = lib.entity(entity)
local data = {}
data.label = entity:getLabel()
data.qid = entity.id
if d == 2 or d == 3 then
return p._linkToEntities(entity)
elseif d == 4 then
return data.label
end
return string.format('No support for rendering entity of type institution at distance level %s.', tostring(d))
end
--------------------------------------------------------------------------------
-- Description item
function p.__description(entity, d)
local entity = lib.entity(entity)
local data = {}
data.id = entity.id
data.instance_of_links = p._instance_of_links(entity)
if d == 1 then
data.document = p._transcludeDocument(data.id)
if data.document == template.HTML.no_data then
-- skip empty or non-existing pages
return ''
else
data.data_lines = p._data_lines(entity, { m.prop.attributed_to,
m.prop.inception,
m.prop.generated_by,
m.prop.copyedited_by})
return template.render('description_1', data)
end
end
return string.format('No support for rendering entity of type description at distance level %s.', tostring(d))
end
--------------------------------------------------------------------------------
-- Summary item (almost same as description)
function p.__summary(entity, d)
local entity = lib.entity(entity)
local data = {}
data.id = entity.id
data.instance_of_links = p._instance_of_links(entity)
if d == 1 then
data.document = p._transcludeDocument(data.id)
if data.document == template.HTML.no_data then
-- skip empty or non-existing pages
return ''
else
data.data_lines = p._data_lines(entity, { m.prop.attributed_to,
m.prop.inception,
m.prop.generated_by,
m.prop.copyedited_by})
return template.render('summary_1', data)
end
end
return string.format('No support for rendering entity of type summary at distance level %s.', tostring(d))
end
--------------------------------------------------------------------------------
-- artist statement item
function p.__artist_statement(entity, d)
local entity = lib.entity(entity)
local data = {}
data.id = entity.id
data.instance_of_links = p._instance_of_links(entity)
if d == 1 then
data.document = p._transcludeDocument(data.id)
if data.document == template.HTML.no_data then
-- skip empty or non-existing pages
return ''
else
data.data_lines = p._data_lines(entity, { m.prop.attributed_to,
m.prop.inception,
m.prop.generated_by,
m.prop.copyedited_by})
return template.render('description_1', data)
end
end
return string.format('No support for rendering entity of type description at distance level %s.', tostring(d))
end
--------------------------------------------------------------------------------
-- Variant item
function p.__variant(entity, d)
local entity = lib.entity(entity)
local data = {}
data.qid = entity.id
data.access_url = entity:formatPropertyValues(m.prop.access_url, {
mw.wikibase.entity.claimRanks.RANK_NORMAL,
mw.wikibase.entity.claimRanks.RANK_PREFERRED }
)['value']
data.type_of_artifact = template.HTML.unknown
local artifacts = lib.statementList{entity, m.prop.artifact} -- , filter=m.item.type_of_artifact}
-- this needs to be expanded to handle multiple artifact types per variant!
if #artifacts > 0 then
local types_of_artifact = lib.statementList{artifacts[1], m.prop.instance_of, filter=m.item.type_of_artifact}
if #types_of_artifact > 0 then
data.type_of_artifact = types_of_artifact[1]:getLabel()
data.instance_of = types_of_artifact[1].id
end
end
if d == 1 then
data.variant_id = entity:getId()
data.inception = lib.statementList{entity, m.prop.inception}
data.inception = p._pointInTime(data.inception)
local instance_of_links = p._instance_of_links(entity)
local data_lines = p._data_lines(entity, { m.prop.not_active_since,
m.prop.access_url,
m.prop.alternative_title,
m.prop.inception,
m.prop.conclusion,
m.prop.generated_by,
m.prop.derived_from,
m.prop.type_of_accession,
m.prop.date_of_accession,
m.prop.associated_with,
m.prop.in_collection_of})
data.data_lines = instance_of_links .. data_lines
return template.render('variant_1', data)
elseif d == 2 then
-- do not display access links for variants that
-- are known to not be active anymore (dead link)
if entity.claims[m.prop.not_active_since] then
return ''
else
if data.type_of_artifact ~= 'outside link' then
data.variant_type = 'ArtBase variant'
end
return template.render('variant_2', data)
end
elseif d == 3 then
return entity:getLabel()
end
return string.format('No support for rendering entity of type variant at distance level %s.', tostring(d))
end
--------------------------------------------------------------------------------
return p