Module:License/sandbox
Jump to navigation
Jump to search
Lua
CodeDiscussionEditHistoryLinksLink count Subpages:DocumentationTestsResultsSandboxLive code All modules
Examples
[edit]Usually {{License from structured data}} is added to a file page. However one can also test the module by using parameters: id (for SDC or Wikidata item number) and debug (a Boolean flag to turn off template rendering).
File | Test | {{License from structured data}} |
---|---|---|
Original license template | ||
Self + cc-by-3.0 + GFDL + attribution |
{{self|Cc-by-3.0|GFDL|attribution=Jarek Tuszyński / / |author=Jarek Tuszyński}} |
|
{{self|cc-by-3.0|GFDL|author=Jarek Tuszyński|attribution=Jarek Tuszyński / CC-BY-3.0 & GDFL}} |
||
FAL + CeCILL + GFDL-1.2 |
{{self|CeCILL|FAL|GFDL-1.2|attribution=Ralf Roletschek / / / |author=Ralf Roletschek}} |
|
{{GFDL-1.2}}{{CeCILL}}{{FAL}} |
||
Cc-by-sa-2.0 + attribution |
{{Cc-by-sa-2.0|attribution=Derelict looking barn, New Pound by N Chadwick / |author=N Chadwick}} |
|
{{Cc-by-sa-2.0|attribution=Derelict looking barn, New Pound by N Chadwick}} |
||
Cc-by-2.0 | {{Cc-by-2.0|attribution=Robin Taylor / |author=Robin Taylor}} |
|
{{Cc-by-2.0}} |
||
cc-zero | {{self|Cc-zero}} |
|
{{Cc-zero}} |
||
PD-self | {{PD-self}} |
|
{{PD-self}} |
||
PD-USGov-USGS + cc-zero |
{{PD-USGov}} |
|
{{PD-USGov-USGS}}{{Cc-zero}} |
||
OGL3 | {{OGL3|attribution=Cpl Ian Houlding / |author=Cpl Ian Houlding}} |
|
{{OGL3|1=Photo: Cpl Ian Houlding/MOD}} |
||
FAL | {{self|Cc-by-sa-3.0|FAL|attribution=spacebirdy / / |author=spacebirdy}} |
|
{{self|Cc-by-sa-3.0|FAL}} |
||
FAL | {{self|Cc-by-sa-4.0|GPLv2+}} |
|
{{self|cc-by-sa-4.0}}{{Free screenshot|{{GPL}}}} |
||
Attribution | {{Attribution only license}} |
|
{{Attribution|nolink=Shiretoko-Shari Tourist Association}} |
||
Cc-by-sa-4.0,3.0 ,2.5,2.0,1.0 |
{{self|Cc-by-sa-4.0,3.0,2.5,2.0,1.0|GFDL|attribution=Agreco / / / / / / |author=Agreco}} |
|
{{self|GFDL|Cc-by-sa-4.0,3.0,2.5,2.0,1.0}} |
||
Cc-by-sa-4.0,3.0 ,2.5,2.0,1.0 +PD-self |
{{Cc-by-sa-4.0,3.0,2.5,2.0,1.0}}
|
|
{{self|GFDL|Cc-by-sa-4.0,3.0,2.5,2.0,1.0}}{{PD-self}} |
||
WTFPL | {{self|Cc-zero|PD-author|WTFPL}} |
|
{{self|cc-zero|WTFPL}} |
||
MIT | {{MIT}} |
|
{{MIT}} |
||
PD-Art PD-old-100-expired |
{{PD-old-100-expired}}
{{PD-Art}} |
|
{{PD-Art|PD-old-100-expired|deathyear=1903}} |
||
from wikidata | {{PD-USGov}} |
|
{{PD-USGov-DOE}} |
Code
--[[
__ __ _ _ _ _
| \/ | ___ __| |_ _| | ___ _| | (_) ___ ___ _ __ ___ ___
| |\/| |/ _ \ / _` | | | | |/ _ (_) | | |/ __/ _ \ '_ \/ __|/ _ \
| | | | (_) | (_| | |_| | | __/_| |___| | (_| __/ | | \__ \ __/
|_| |_|\___/ \__,_|\__,_|_|\___(_)_____|_|\___\___|_| |_|___/\___|
This module is intended to be the engine behind "Template:License from structured data"
Please do not modify this code without applying the changes first at
"Module:License/sandbox" and testing at "Template:License/testcases".
Authors and maintainers:
* User:Jarekt - original version
]]
-- =======================================
-- === Dependencies ======================
-- =======================================
local core = require('Module:Core')
local sdc_author = require('Module:Information')._SDC_Author
-- =======================================
-- === Global variables ==================
-- =======================================
local P = { -- define all the special Ps and Qs used by the program and give them names
instance_of = 'P31', -- instance of
copyright_license = 'P275', -- copyright license
determination_method = 'P459', -- determination method
applies_to_part = 'P518', -- applies to part
official_website = 'P856', -- official website
full_work_URL = 'P953', -- full work available at URL
jurisdiction = 'P1001', -- applies to jurisdiction
main_template = 'P1424', -- main template
title = 'P1476', -- title
duration = 'P2047', -- duration
author_name_str = 'P2093', -- author name string
relative_to = 'P2210', -- relative to
copyright_status = 'P6216', -- copyright status
file_source = 'P7482', -- source of file
attribution_text = 'P8264', -- attribution text
}
local Q = {
public_domain = 'Q19652', -- public domain
copyrighted = 'Q50423863', -- copyrighted
copyrighted_pd = 'Q88088423', -- copyrighted, dedicated to the public domain by copyright holder
own_work = 'Q66458942', -- original creation by uploader
death_date = 'Q108697943', -- date of death of author(s)
}
local suported_license_familys = {
'Q284742', -- Creative Commons license family
'Q22169', -- GNU Free Documentation License family
'Q196294', -- free license
'Q1156659', -- OSI-approved license
'Q3943414', -- free software license
'Q5975031', -- copyleft free software license
'Q25047642', -- public domain equivalent license
}
local multi_license_templates = { -- use list instead of dictionary-like table to preserve order
{'Cc-by-sa-4.0,3.0,2.5,2.0,1.0', {'Cc-by-sa-1.0', 'Cc-by-sa-2.0', 'Cc-by-sa-2.5', 'Cc-by-sa-3.0', 'Cc-by-sa-4.0'}}, -- 400k
{'Cc-by-sa-3.0,2.5,2.0,1.0', {'Cc-by-sa-1.0', 'Cc-by-sa-2.0', 'Cc-by-sa-2.5', 'Cc-by-sa-3.0'}}, -- 1.1M
{'Cc-by-sa-2.5,2.0,1.0', {'Cc-by-sa-1.0', 'Cc-by-sa-2.0', 'Cc-by-sa-2.5'}}, -- 288k
{'Cc-by-4.0,3.0,2.5,2.0,1.0', {'Cc-by-1.0', 'Cc-by-2.0', 'Cc-by-2.5', 'Cc-by-3.0', 'Cc-by-4.0'}}, -- 500 uses
{'Cc-by-3.0,2.5,2.0,1.0', {'Cc-by-1.0', 'Cc-by-2.0', 'Cc-by-2.5', 'Cc-by-3.0'}}, -- 17k uses
{'PD-old-100-expired', {'PD-old-100', 'PD-US-expired'}},
{'PD-old-95-expired', {'PD-old-95', 'PD-US-expired'}},
{'PD-old-90-expired', {'PD-old-90', 'PD-US-expired'}},
{'PD-old-80-expired', {'PD-old-80', 'PD-US-expired'}},
{'PD-old-75-expired', {'PD-old-75', 'PD-US-expired'}},
{'PD-old-70-expired', {'PD-old-70', 'PD-US-expired'}},
{'PD-old-60-expired', {'PD-old-60', 'PD-US-expired'}},
{'PD-old-50-expired', {'PD-old-50', 'PD-US-expired'}},
{'PD-old-100-1996', {'PD-old-100', 'PD-US-1996'}},
{'PD-old-95-1996', {'PD-old-95', 'PD-US-1996'}},
{'PD-old-90-1996', {'PD-old-90', 'PD-US-1996'}},
{'PD-old-80-1996', {'PD-old-80', 'PD-US-1996'}},
{'PD-old-75-1996', {'PD-old-75', 'PD-US-1996'}},
{'PD-old-70-1996', {'PD-old-70', 'PD-US-1996'}},
{'PD-old-60-1996', {'PD-old-60', 'PD-US-1996'}},
{'PD-old-50-1996', {'PD-old-50', 'PD-US-1996'}},
}
local find_and_replace = {
{'{{self|PD-author}}', '{{PD-self}}'}
}
-- ==================================================
-- === Generic Local functions ======================
-- ==================================================
-------------------------------------------------------------------------------
local function getProperty(entity, prop)
return (core.parseStatements(entity:getBestStatements( prop ), nil) or {nil})[1]
end
-------------------------------------------------------------------------------
local function getAllProperties(entity, prop)
return core.parseStatements(entity:getAllStatements( prop ), nil)
end
-------------------------------------------------------------------------------
local function getAllItemProperties(item, prop)
return core.parseStatements(mw.wikibase.getAllStatements( item, prop ), nil)
end
-------------------------------------------------------------------------------
local function getItemProperty(item, prop)
return (core.parseStatements(mw.wikibase.getBestStatements( item, prop ), nil) or {nil})[1]
end
-- ===========================================================================
local function union(set1, set2)
-- find union of two sets
for __, item2 in ipairs(set2) do
table.insert(set1, item2)
end
return set1
end
-- ===========================================================================
local function intersect(set1, set2)
-- find intersect of two sets
local intersct = {}
for __, item1 in ipairs(set1) do
for __, item2 in ipairs(set2) do
if item1 == item2 then
table.insert(intersct, item1)
break
end
end
end
return intersct
end
-- ===========================================================================
local function contains(element, list)
for __, item in ipairs(list) do
if item == element then
return true
end
end
return false
end
-- ===========================================================================
local function difference(set1, set2)
-- find difference of two sets
local diff = {}
local found
for __, item1 in ipairs(set1) do
found = false
for __, item2 in ipairs(set2) do
if item1 == item2 then
found = true
break
end
end
if not found then
table.insert(diff, item1)
end
end
return diff
end
-- ===========================================================================
local function filter_statements(statments, pvals)
-- from a list of "statements" remove those there the main value is not in the set "pvals"
local res = {}
for _, statement in ipairs(statments) do
local snak = statement.mainsnak
if (snak.snaktype == "value" and contains(snak.datavalue.value.id, pvals) and
snak.datavalue.value and statement.rank ~= 'deprecated') then
table.insert(res, statement)
end
end
return res
end
-- ===========================================================================
local function parse_snak(snak)
local val = snak.datavalue.value
if val.id then
val = val.id
elseif val.text then
val = val.text
elseif val.amount then
val = tonumber(val.amount)
end
return val
end
-- ===========================================================================
local function getQuals(statements, qual)
-- get all the qualifiers "qual" from a list of "statements"
local res = {}
for _, statement in ipairs( statements ) do
if (statement.mainsnak.snaktype == "value" and statement.qualifiers and statement.qualifiers[qual]) then
for _, snak in ipairs( statement.qualifiers[qual] ) do
if (snak.snaktype == "value" and snak.datavalue.value) then
table.insert(res, parse_snak(snak))
end
end
end
end
return res
end
-- ===========================================================================
local function getQual(entity, prop, qual)
-- get the first qualifier "qual" of a property "prop"
local statements = entity:getBestStatements( prop )
return (getQuals(statements, qual))[1]
end
-- ===========================================================================
local function starts_with(text, prefix)
-- string function testing of strings "text" starts with "prefix"
return string.sub(text,1,string.len(prefix))==prefix
end
-- ==================================================
-- === Copyright specific local functions ===========
-- ==================================================
-- ===========================================================================
local function getQuals2(statements, quals, main_prop)
--[[ get all the qualifiers P["qual"] from a list of "statements"
Inputs:
1: statements - output of entity:getBestStatements( prop )
2: quals - list of strings with qualifier names
3: P - look up table converting qualifier names to property numbers
]]
local res = {}
for _, statement in ipairs( statements ) do
local bundle = {}
for _, qual in ipairs( quals ) do
local prop = P[qual]
if (statement.mainsnak.snaktype == "value" and statement.qualifiers and statement.qualifiers[prop]) then
for _, snak in ipairs( statement.qualifiers[prop] ) do
if (snak.snaktype == "value" and snak.datavalue.value) then
bundle[qual] = parse_snak(snak)
end
end
end
end
local snak = statement.mainsnak
if (snak.snaktype == "value") and (statement.rank ~= 'deprecated') then
bundle[main_prop] = parse_snak(snak) -- key is property name not code
end
table.insert(res, bundle)
end
return res
end
-- ===========================================================================
--[[
Look through template list and see
Inputs:
1: bundles - list of dictionary-like data structures storing copyright "bundles".
Each bundle can have different license for different copyright holder,
applying to different countries or part of the item.
Outputs:
1: bundles - altered list
]]
local function aggregate_multi_license_templates (bundles)
if #bundles<2 then
return bundles, ''
end
local license_tags = {}
for __, bundle in ipairs(bundles) do
table.insert(license_tags, bundle['template_name'])
if bundle['attribution_text'] or bundle['applies_to_part'] then
-- do not aggregate if individual attributions applies_to_part used
--return bundles, 'attr'
end
end
local msg = {'.'}
for _, rec in ipairs(multi_license_templates) do
local parent_temp, template_list = rec[1], rec[2]
local intrsct = intersect(license_tags, template_list)
table.insert(msg, tostring(#intrsct))
if #intrsct == #template_list then -- all license_tags are contained in lic_list -> remove them and replace with parent_temp
local bundles2 = {{['template_name'] = parent_temp}}
for _, bundle in ipairs(bundles) do
if not contains(bundle['template_name'], template_list) then
table.insert(bundles2, bundle) -- keep other templates
else
bundles2[1]['copyright_status'] = bundle['copyright_status']
end
end
return bundles2, table.concat(msg, ',')
end
end
return bundles, table.concat(msg, ',')
end
-- ===========================================================================
--[[
Build attribution string based on provided elements
Inputs:
1: bundles - list of dictionary-like data structures storing copyright "bundles".
2: author_str - author string (optional)
3: title_str - title string (very optional)
Outputs:
1: attribution string
]]
local function get_default_attribution(bundles, author_str, title_str)
local attr_list, link = {}, nil
if author_str and title_str then
author_str = mw.ustring.format("''%s'' by %s", title_str, author_str)
end
if not author_str then
return nil
end
table.insert(attr_list, author_str)
for _, bundle in ipairs(bundles) do
local lic = bundle['template_name']
local url = bundle['website']
if url then
link = '<span class="plainlinks noprint">[' .. url .. ' ' .. lic .. ']</span>'
else
link = '[[Template:' .. lic .. '|' .. lic .. ']]'
end
table.insert(attr_list, link)
end
return table.concat(attr_list, ' / ')
end
-- ===========================================================================
--[[
add attribution and author parameters to a license tag template
Inputs:
1: license - license tag string
2: author - author string (can be nil)
3: attribution - attribution string (can be nil)
Outputs:
1: altered string with license template wikicode
]]
local function add_to_license_tag(license, author, attribution)
if attribution then
license= string.gsub(license, '}}$', '|attribution=' .. attribution .. '}}') -- add attribution to {{self}}
end
if author then
license = string.gsub(license, '}}$', '|author=' .. author .. '}}') -- add author to {{self}}
end
return license
end
-- ===========================================================================
--[[
form wikicode for {{self}} template based on provided elements
Inputs:
1: license_tags - list of license templates names
2: author_str - author string (optional)
3: attribution_str - attribution string (optional)
Outputs:
1: string with wikicode for {{self}} template
]]
local function form_self_template(bundles, author_str, attribution_str)
local license_seg, pd_seg, num_cr = {'self'}, {}, 0
for _, bundle in ipairs(bundles) do
local status = bundle['copyright_status']
if status == Q['copyrighted'] then
table.insert(license_seg, bundle.template_name)
num_cr = num_cr + 1
elseif status == Q['copyrighted_pd'] then
table.insert(license_seg, bundle.template_name)
elseif status == Q['public_domain'] then
table.insert(pd_seg, '{{' .. bundle.template_name .. '}}')
end
end
local license_tag = '{{' .. table.concat(license_seg, '|') .. '}}'
if num_cr>0 then
license_tag = add_to_license_tag(license_tag, author_str, attribution_str)
end
table.insert(pd_seg, 1, license_tag)
return pd_seg
end
-- ===========================================================================
local function harvest_copyright_data(entity)
-- local P, Q = mind_your_Ps_and_Qs() -- give names to often used items and properties
local quals = {'copyright_license', 'attribution_text', 'applies_to_part',
'jurisdiction', 'determination_method'}
-- collect both copyright_status and copyright_license statements
local cs_statements = entity:getBestStatements( P['copyright_status'] )
local cl_statements = entity:getBestStatements( P['copyright_license'] )
-- keep only "copyrighted" statements and skip "Public domain" ones
--cs_statements = filter_statements(cs_statements, {Q['copyrighted'], Q['copyrighted_pd']})
local bundles
if #cs_statements == 0 then
return {} -- no copyright statements found
elseif #cs_statements >= 1 and #cl_statements == 0 then
-- Wikidata style 'bundles' organized as qualifiers of copyright_status
bundles = getQuals2(cs_statements, quals, 'copyright_status')
elseif #cs_statements == 1 and #cl_statements >= 1 then
-- Commons style: single copyright_status and bunch of copyright_licenses, possibly with qualifiers
bundles = getQuals2(cl_statements, quals, 'copyright_license')
local cs = (core.parseStatements(cs_statements, nil) or {nil})[1]
for __, bundle in ipairs(bundles) do
bundle['copyright_status'] = cs
end
else
return {}
end
return bundles
end
-- ===========================================================================
local function get_free_licenses(entity, args)
-- Set error messages
local error_msg = {
bad_license = 'license [[d:%s]] does not belong to a recognized license family',
missing_template = 'item [[d:%s]] is missing main template (P1424) property',
missing_sitelink = 'missing sitelink to Commons for item [[d:%s]]',
bad_sitelink = 'sitelink to Commons for item %s does not point to a template, but to "%s"',
bad_jurisdiction = 'jurisdiction of "determination_method" %s does not match jurisdiction of the item',
issue = 'copyright_status = %s; license_item = %s; det_metchod = %s; jurisdiction = %s'
}
-- collect licenses
-- license info can be either in "copyright license" property or as qualifiers of "copyright_status" property
local bundles = harvest_copyright_data(entity)
if not bundles or #bundles == 0 then
return {} -- no copyright statements found
end
-- get commons templates based on license bundles
local template_name, template_item
for _, bundle in ipairs(bundles) do
local license_item = bundle['copyright_license']
local det_metchod = bundle['determination_method']
local status = bundle['copyright_status']
if contains(status, {Q['copyrighted'], Q['copyrighted_pd']}) and license_item then
local instance_of = getAllItemProperties(license_item, P['instance_of'])
bundle['website'] = getItemProperty(license_item, P['official_website'])
if table.maxn(intersect(instance_of, suported_license_familys))==0 then
return mw.ustring.format(error_msg['bad_license'], license_item)
end
-- The license (like Creative Commons Attribution-ShareAlike 4.0 International (Q18199165)))) should have a
-- topic's main template (P1424) (Template:Cc-by-sa-4.0 (Q15243492)) that links to a valid template on Commons.
template_item = getItemProperty(license_item, P['main_template'])
if not template_item then -- missing sitelink to Commons to Commons template
return mw.ustring.format(error_msg['missing_template'], template_item)
end
elseif contains(status, {Q['public_domain'], Q['copyrighted_pd']}) and det_metchod then
local jurisdiction = getItemProperty(det_metchod, P['jurisdiction'])
if jurisdiction ~= bundle['jurisdiction'] then
return mw.ustring.format(error_msg['bad_jurisdiction'], det_metchod)
end
template_item = getItemProperty(det_metchod, P['main_template'])
if not template_item then -- missing sitelink to Commons to Commons template
return mw.ustring.format(error_msg['missing_template'], det_metchod)
end
--[[if not template_name then -- missing sitelink to Commons template
local jurisdiction = getItemProperty(det_metchod, P['jurisdiction'])
for duration in getQuals2(entity:getBestStatements(P['duration']), {'relative_to'}, P) do
if duration['relative_to'] == Q['death_date'] then
end
end
end ]]
else
-- return mw.ustring.format(error_msg['issue'], status or 'nil', license_item or 'nil',
-- det_metchod or 'nil', bundle['jurisdiction'] or 'nil')
return mw.text.jsonEncode(bundle)
end
template_name = mw.wikibase.getSitelink(template_item)
if not template_name then -- missing sitelink to Commons to Commons template
return mw.ustring.format(error_msg['missing_sitelink'], template_item)
end
if not starts_with(template_name, 'Template:') then -- sitelink to Commons does not point to a template
return mw.ustring.format(error_msg['bad_sitelink'], template_item, template_name)
end
bundle['template_name'] = string.gsub(template_name, '^Template:', '', 1) -- remove 'Template:' prefix
end
-- sort bundles
local sort_fun = function(a,b) return a.template_name < b.template_name end
table.sort(bundles, sort_fun) -- sort license names so they always show up in the same order
-- get attribution
-- If the license has author name string (P2093) and title (P1476), use that to construct the attribution
local author_str = getQual(entity, P['copyright_license'], P['author_name_str'])
author_str = author_str or sdc_author(entity, args.lang, false) -- if no author than use the author shown in {{information}} template
local title_str = getQual(entity, P['copyright_license'], P['title'])
local attribution = args.attribution or getProperty(entity, P['attribution_text']) or
get_default_attribution(bundles, author_str, title_str)
-- Assemble template list, one template per line
local msg
bundles, msg = aggregate_multi_license_templates(bundles)
local tag_list = {}
if getProperty(entity, P['file_source']) == Q['own_work'] then
-- if source of file (P7482) -> original creation by uploader (Q66458942) then wrap the licenses in {{Self}}
tag_list = form_self_template(bundles, author_str, attribution)
else -- return all licenses as a list
for _, bundle in ipairs(bundles) do -- add individual license to a list
local license_tag = '{{' .. bundle['template_name'] .. '}}'
if bundle['copyright_status'] == Q['copyrighted'] then
license_tag = add_to_license_tag(license_tag, author_str, attribution)
end
table.insert(tag_list, license_tag)
end
end
--table.insert(tag_list, '[' .. msg .. ']')
-- find and replace some tags
for itag, tag in ipairs(tag_list) do
for _, pair in ipairs(find_and_replace) do
if tag == pair[1] then
tag_list[itag] = pair[2] --string.gsub( tag, pair[1], pair[2], 1 )
end
end
end
return tag_list
end
-- ===========================================================================
local function get_public_domain_tags(entity, args)
--(not implemented yet)
return {}
end
-- ==================================================
-- === External functions ===========================
-- ==================================================
local p = {}
-- ===========================================================================
-- === Version of the function to be called from other LUA codes
-- ===========================================================================
function p.copyright_tags(entity, args)
local functions = { -- list of functions for gathering copyright tags
get_free_licenses, -- look for free licenses
get_public_domain_tags -- look for Public Domain tags
}
local tag_list ={}
for _, get_tags in ipairs(functions) do
local tags = get_tags(entity, args)
if type(tags) == 'string' then
return tags -- return error message
else -- add to the list of tags
for _, tag in ipairs(tags) do
table.insert(tag_list, tag)
end
end
end
return tag_list
end
-- ===========================================================================
-- === Version of the functions to be called from template namespace
-- ===========================================================================
function p.SDC_license(frame)
local args = core.getArgs(frame)
-- get entity
local page = mw.title.getCurrentTitle()
local item_id, entity = args.id, nil
if (item_id==nil) and (page.namespace == 6) then
entity = mw.wikibase.getEntity()
elseif item_id and type(item_id)=='string' and item_id:match( '^[QqMm]%d+$' ) then
entity = mw.wikibase.getEntity(item_id)
elseif item_id and type(item_id)~='string' and item_id.id then
entity = item_id -- entities can be passed from outside
else
return ''
end
if (not entity) then
return ''
end
local tag_list = p.copyright_tags(entity, args)
if type(tag_list) == 'string' then
return tag_list -- return error message
end
local tags = table.concat(tag_list, '\n')
if not args.debug then -- in debug mode do not expand templates
tags = frame:preprocess(tags)
end
return tags
end
return p