Модуль:Таксобокс

Матеріал з Вікіпедії — вільної енциклопедії.
Перейти до навігації Перейти до пошуку
{{i}} Документація модуля[створити]
local p = {}
local initialitem
local lang
local usereferences = {} -- array of references to be preffered in the given order
local hierarchy = {}
local taxondetails = ''
local code = false
local visited = {}
--                                 ICZN               ICNafp              ICNCP            BC/ICNP             ICVCN
local colors = {[false]='#d3d3d3', [13011]='#d3d3a4', [693148]='#9bcd9b', [764]='#a4d3d3', [743780]='#d3a4d3', [14920640]='#2f6fab'} -- background colors for each code

function p.taxobox(frame)
    local config = frame.args
    
    local count = tonumber(config.count) or 10
    if count > 10 then
       return 'count too high'
    end
    
    lang = config.lang or 'uk'

	p.parsereferences(config.references)
    return p.createbox(config.qid or mw.wikibase.getEntity().id, count)
end

-- parse references argument which is a space separated list of item numbers like "Q1 Q2 Q3" which should be preferred as references, i.e., if possible the parent taxon is choosen according to this priority list
function p.parsereferences(references)
	if references then
        for word in string.gmatch(references, "%w+") do
        	ref = tonumber(string.sub(word, 2))
        	mw.log('selected ref', ref)
            table.insert(usereferences, ref)
        end
    end
end

-- creates the taxobox for the given qid (e.g., qid=Q729412 for Heloderma) and count higher levels of the taxon hierarchy.
-- developers: use this method for tests in the debug console, e.g., p.createbox('Q729412', 5)
function p.createbox(qid, count)
	visited = {}
    local content, references = p.iterate(qid, count, true, true)
    if not content then
    	return
    end
    local item = initialitem
    
    local header = mw.text.tag( 'tr', {}, mw.text.tag( 'th', { colspan='2', style='text-align: center; background-color: ' .. colors[code] }, p.getItemLabel(item)))
    
    local image = p.targetStr(item, 'P18')
    if image then
       header = header .. mw.text.tag( 'tr', {}, mw.text.tag( 'td', {colspan='2'}, '[[File:' .. image .. '|220px|.]]'))
    end
    
    header = header .. p.fossilInfo(item)

    local refstr = p.references(references)
    header = header .. mw.text.tag( 'tr', {}, mw.text.tag( 'th', { colspan='2', style='text-align: center; background-color: ' .. colors[code] }, '[[Q3516404|'.. (p.getItemLabel(mw.wikibase.getEntityObject('Q3516404'))) ..']]' .. refstr))
    mw.log('header', header)
    mw.log('hierarchy', content)
    content = header .. content
    
    content = content .. taxondetails
    mw.log('nomenclature', taxondetails)
    
    footer =  p.map(item) .. p.iucn(item) .. p.audio(item)
    mw.log('footer', footer)
    content = content .. footer
    
    return mw.text.tag( 'table', { style = "width: 200px; border-width: 1px; border-style: solid; background-color: #f9f9f9; float: right;" }, content )
end

-- performs the loop up the hierarchy using P171 (parent taxon)
function p.iterate(qid, count, first, showtaxoname)
    local item = mw.wikibase.getEntityObject(qid)
    
    name = p.targetStr(item, 'P225')
    if name == 'nil' then
    	return
    end
    
    local nextid, references = p.chooseparent(item)
    mw.log('nextid', nextid)
    if not code then
		codeid = next(p.targetId(item, 'P944')) -- code of nomenclature
        if codeid and colors[codeid] then
           	code = codeid
        end
    end
    
    if visited[nextid] then -- loop detection
    	return '', {}
    elseif nextid then
    	visited[nextid] = true
    end
    
    local showtaxoname = first or (showtaxoname and next(p.targetId(item, 'P31')) == 310890)
    local content = ''
    if nextid and (not code or count > 0) then
        output, refs = p.iterate('Q' .. nextid, count - 1 , false, showtaxoname)
        for ref,_ in pairs(refs) do
            references[ref] = true
        end
        content = content .. output
    end
    if count > 0 then
    	nextcontent = p.itemOutput(qid, item, first, showtaxoname)
    	if nextcontent then
    		content = content .. nextcontent
    	end
    end
    return content, references
end

-- in case of more than one parent taxa: choose target according to the references selected by usereferences
function p.chooseparent(item)
    local nextid = {} -- list of targets from which the first one will be used
    local references = {}
    local candnextid, candreferences
    mw.log('chooseparent', item)
    for i,j in pairs(usereferences) do
		mw.log('usereferences', i, j)
	end
    for id,refs in pairs(p.targetId(item, 'P171')) do
        if refs then
            for i,r in pairs(usereferences) do
            	mw.log('testing', r, 'as reference')
                if refs[r] then
                	mw.log('found', r)
                    nextid[i] = id
                    references[i] = refs
                end
            end
            if not candreferences then
				candreferences = refs
			end
        else
        	mw.log('no refs found')
    	end
    	
        if not candnextid then -- if no item had references yet
            candnextid = id -- use this
        end
    end
    if next(nextid) then
        _,candnextid = next(nextid)
    end
    if next(references) then
        _,candreferences = next(references)
    else -- a new reference is used, append it to usereferences for further usage
    	if candreferences then
    		for targetid,_ in pairs(candreferences) do
    			table.insert(usereferences, targetid)
    			mw.log('appended ref', targetid)
			end
		end
	end
    return candnextid, candreferences or {}
end

-- formats each item in the taxon hierarchy by using p105 (taxon rank) and p225 (scientific name)
function p.itemOutput(qid, item, first, showtaxonname)
    local rankid
    rankid = next(p.targetId(item, 'P105'))
    
    -- format rank
    local rankstr = mw.text.tag( 'td', {}, 'unknown rank')
    local ranklink = 'taxon'
    if rankid then
    	if rankid == 'novalue' then
    		rankstr = mw.text.tag( 'td', {}, '')
		else
        	local rankitem = mw.wikibase.getEntityObject('Q' .. rankid)
        	if rankitem then
        		ranklink = '[[Q' .. rankid .. '|' .. p.getItemLabel(rankitem) .. ']]'
	            if rankid == 713623 then
    	            rankstr = mw.text.tag( 'td', {}, '')
        	    else
            	    rankstr = mw.text.tag( 'td', {}, ranklink)
        	    end 
        	end
        end
    end
    
    -- format vernacular and scientific names
    local vernacularname = p.vernacularname(item)

    local name, namequalifiers, namereferences = p.targetStr(item, 'P225') -- scientific name

    local plainname = name or 'no scientific name'
    if rankid then
    	hierarchy[rankid] = plainname
    end
    if rankid == 7432 or rankid == 68947 or rankid == 34740 or rankid == 3238261 then -- italic in case of species (7432) or subspecies (34740) or genus (34740) or subgenus(3238261) rank
       plainname = mw.text.tag('i', {}, plainname)
    end
    if vernacularname and showtaxonname then -- print only trivial label if taxon details are printed
      plainname = mw.text.tag('td', {}, '[[' .. qid .. '|' ..  vernacularname .. ']]')
    elseif vernacularname then
    	plainname = mw.text.tag('td', {}, '[[' .. qid .. '|' ..  vernacularname .. ']] (' .. plainname .. ')')
	else
       plainname = mw.text.tag('td', {}, '[[' .. qid .. '|' ..  plainname .. ']]')
    end

    if first then
       initialname = taxonname
       initialitem = item
    end
    if showtaxonname then
        p.taxondetails(item, name, rankid, ranklink, namequalifiers, namereferences)
    end
    return mw.text.tag( 'tr', {}, rankstr .. plainname)
end

function p.vernacularname(item)
	local vernacularname = item:formatPropertyValues('P1843') -- vernacular name
    if vernacularname then
    	vernacularname = vernacularname['value']
    	if vernacularname == '' then
    		vernacularname = nil
		end
	end

    if not vernacularname then
    	vernacularname = p.getItemLabel(item) -- test if item label is not one of the scientific names
    	scnames = p.mergeClaims(p.targetStrs(item, 'P225'))
		for _,n in pairs(scnames) do
			if vernacularname == n then
				return
			end
		end
	end
	return vernacularname
end

-- shows detailed information for current and all next monotypic taxa
function p.taxondetails(item, name, rankid, rank, namequalifiers, namereferences)
    mw.log('show details for', name, rank, code)
    
    -- remove already displayed references
    if namereferences then
        for refid,_ in pairs(namereferences) do
            mw.log('name reference', refid)
        end
    end
    local refstr = p.references(namereferences)
    taxondetails = taxondetails .. mw.text.tag('tr', {}, mw.text.tag( 'th', { colspan="2", style='text-align: center; background-color: ' .. colors[code] }, 'scientific name of ' .. rank .. refstr))
    
    local fullname = name
    if rankid == 3238261 and p.icnafpApplies() then -- format subgenus according to ICNafp §5A.1. (genus name is part of P225)
    	strstart, strend = string.find(fullname, "%s+subg%.%s+")
    	if strstart and strend then
    		fullname = mw.text.tag('i', {}, string.sub(fullname, 1, strstart)) .. ' subg. ' .. mw.text.tag('i', {}, string.sub(fullname, strend))
    	end
	elseif rankid == 3238261 and hierarchy[34740] then -- format subgenus according to ICZN (genus name is not part of P225)
		fullname = mw.text.tag('i', {}, hierarchy[34740] .. ' (' .. fullname .. ')')
    elseif rankid == 7432 or rankid == 68947 or rankid == 34740 or rankid == 3238261 then -- italic in case of species (7432) or subspecies (34740) or genus (34740) or subgenus(3238261) rank
       fullname = mw.text.tag('i', {}, fullname)
    end
    
    taxondetails = taxondetails .. mw.text.tag('tr', {}, mw.text.tag( 'td', {colspan="2", style="text-align: center"}, fullname))
    
    local year = p.qualifierTargetTime(namequalifiers, 'P574') or p.targetTime(item, 'P574') -- date of taxon name publication
    if year then
       year = string.sub(year, 2, 5) -- access year in time representation "+1758-00-00T00:00:00Z"
    else
       year = '????'
    end

	local authorsstr = p.createAllAuthorsStr(item, namequalifiers, year)
	if authorsstr then
		taxondetails = taxondetails ..  mw.text.tag( 'tr', {}, mw.text.tag( 'td', {colspan="2", style="text-align: center; font-variant:small-caps"}, authorsstr))
	end
end

-- create the taxon authors string, including year, ex authors and authors of the basionym
function p.createAllAuthorsStr(item, namequalifiers, year)
    local authors = p.authorString(item, namequalifiers)
    local authorsstr = ''
    if authors or not year == '????' then
        if p.icnafpApplies() then
            -- check for basionym
            local basionymids = p.targetId(item, 'P566')
            local basionymstr = ''
            if next(basionymids) then
                local basionym = mw.wikibase.getEntityObject('Q' .. next(basionymids))
	            local _,basionymnamequalifiers = p.targetStr(basionym, 'P225')
	            basionymstr = p.createAllAuthorsStr(basionym, basionymnamequalifiers)
                if basionymstr then                
                	basionymstr = '(' .. basionymstr .. ') '
                end
            end
            
            -- check ex-authors
            local exauthors = p.authorString(nil, namequalifiers, 'P697')
            exauthorsstr = ''
            mw.log(exauthors)
            if exauthors then
                exauthorsstr = exauthors .. ' ex ' 
            end
            authorsstr =  basionymstr .. exauthorsstr .. authors
            if year then
            	authorsstr = authorsstr .. ' (' .. year .. ')'
            end
        else
            authorsstr = authors .. ', ' .. year
                
    		-- parentheses needed if instance of recombination
    		local recombination = false
    		for _,tid in pairs(p.qualifierTargetId(namequalifiers, 'P31')) do
        		if tid == 14594740 then
            		recombination = true
        		end
    		end
    
            if recombination then
                authorsstr = '(' .. authorsstr .. ')'
            end
        end
        return authorsstr
    end
end

function p.authorString(item, namequalifiers, pid)
    if not pid then
        pid = 'P405' -- property
    end
    local authorids = p.qualifierTargetId(namequalifiers, pid) -- get qualifiers
    if not next(authorids) then -- no qualifiers found, check properties
    	local authorset = p.targetId(item, pid)
        local authors = {}
        if authorset then -- create list from set
          	authorids = {}
           	for author,_ in pairs(authorset) do
           		table.insert(authorids, author)
           	end
        end	
    end
    local authors = p.createLinks(authorids, true)
    if next(authors) then
        local concatstr =  ', '
        local last = table.remove(authors)
        local rest = ''
        if #authors > 0 then
            rest = table.concat(authors, ', ') .. ' & '
        end
        return rest .. last
    end
end

-- check if International Code of Nomenclature for algae, fungi, and plants (ICNafp) applies
function p.icnafpApplies()
    return code == 693148
end

-- show the stratigraphic range in which an extinct fossil existed
function p.fossilInfo(item)
	local info = ''
	local era1, era1references = next(p.targetId(item, 'P523'))
    local era2, era2references = next(p.targetId(item, 'P524'))
    if era1 and era2 and not (era1 == 'novalue' or era2 == 'novalue') then
       	local era1item = mw.wikibase.getEntityObject('Q' .. era1)
       	if era1item then
        	eralink = '[[Q' .. era1 .. '|' .. p.getItemLabel(era1item) .. ']]'
        	if not (era1 == era2) then
        		local era2item = mw.wikibase.getEntityObject('Q' .. era2)
       			if era2item then
       				eralink = eralink .. '—[[Q' .. era2 .. '|' .. p.getItemLabel(era2item) .. ']]'
   				end
   			end
   			
   			for a, b in pairs(era2references) do -- show references for both, era1 and era2, only once
   				era1references[a] = b
   			end
   			local refstr = p.references(era1references)
   			
    		info = info .. mw.text.tag( 'tr', {}, mw.text.tag( 'th', {colspan='2', style='text-align: center; background-color: ' .. colors[code]}, '[[Q630830|era]]' .. refstr))
    		info = info .. mw.text.tag( 'tr', {}, mw.text.tag( 'td', {colspan='2', style='text-align: center;'}, eralink))
		end
	end
	return info
end

-- the label of the item if present in the specified language or 'no label'
function p.getItemLabel(item)
   local label = 'no label'
	if item then
		label = item:getLabel(lang) or label
	end
	return label	
end

-- Gives the first highest ranked claim and its references.
-- use only if the data type of the property is item
function p.targetId( item, property)
    local claims = p.targetIds(item, property)
    if next(claims.preferred) then
    	return claims.preferred
    end
	if next(claims.normal) then
		return claims.normal
	end
	return claims.deprecated
end

-- Collect all claims of the given property of the item
-- Returns all claims and their references in tables combined by the claims' rank.
-- result.preferred[target id of claim] = [target id of P248 reference]
-- use only if the data type of the property is item
function p.targetIds(item, property)
    local claims = {preferred = {}, normal = {}, deprecated = {}}
    if item and item.claims and item.claims[property] then
        for _,claim in pairs(item.claims[property]) do
            	if claim.mainsnak.datavalue and claim.mainsnak.datavalue.value then
                	local valueid = claim.mainsnak.datavalue.value['numeric-id']
                
                	local refids = {}
                	if claim.references then
                    	for _,ref in pairs(claim.references) do
                    		if ref.snaks['P248'] then
                        		for prop, refclaim in pairs(ref.snaks['P248']) do
                                	refids[tostring('Q' .. refclaim.datavalue.value['numeric-id'])] = true
                            	end
                        	end
                    	end
                	end
                	claims[claim.rank][valueid] = refids
            	else
            		claims[claim.rank]['novalue'] = true -- snaktype not value
        		end
        end
    end
	return claims
end

-- Gives the first highest ranked claim and its qualifiers and references.
-- Use only if the data type of the property is string
function p.targetStr(item, property)
	choosenclaim, choosenqualifiers, choosenreferences = p.targetStrs(item, property)
	
    index = next(choosenclaim['preferred'])
    if index then
    	return choosenclaim['preferred'][index], choosenqualifiers['preferred'][index], choosenreferences['preferred'][index]
	end
	index = next(choosenclaim['normal'])
    if index then
    	return choosenclaim['normal'][index], choosenqualifiers['normal'][index], choosenreferences['normal'][index]
	end
	index = next(choosenclaim['deprecated'])
    if index then
    	return choosenclaim['deprecated'][index], choosenqualifiers['deprecated'][index], choosenreferences['deprecated'][index]
	end
    return
end

-- Collect all claims of the given property of the item
-- Returns a triple of claims, their qualifiers, and their references in tables combined by the claims' rank.
-- Use only if the data type of the property is string
function p.targetStrs(item, property)
	choosenclaim = {preferred = {}, normal = {}, deprecated = {}}
	choosenqualifiers = {preferred = {}, normal = {}, deprecated = {}}
	choosenreferences = {preferred = {}, normal = {}, deprecated = {}}
    if item and item.claims and item.claims[property] then
        for _,claim in pairs(item.claims[property]) do
        	if claim.mainsnak and claim.mainsnak.datavalue then
				index = #choosenclaim[claim.rank] + 1
				mw.log(index, claim.mainsnak.datavalue.value)
				
				local refids = {}
				if claim.references then
					for _,ref in pairs(claim.references) do
						if ref.snaks['P248'] then
							for prop, refclaim in pairs(ref.snaks['P248']) do
								refids['Q' .. refclaim.datavalue.value['numeric-id']] = true
								mw.log('string ref', refclaim.datavalue.value['numeric-id'])
                            end
                        end
                    end
                end
				if claim.mainsnak.datatype == 'monolingualtext' then
                	choosenclaim[claim.rank][index] = tostring(claim.mainsnak.datavalue.value)
                else
                	choosenclaim[claim.rank][index] = tostring(claim.mainsnak.datavalue.value)
            	end
                choosenqualifiers[claim.rank][index] = claim.qualifiers
                choosenreferences[claim.rank][index] = refids
            end
        end
    end

    return choosenclaim, choosenqualifiers, choosenreferences
end

-- helper function to merge all claims, regardless of rank
function p.mergeClaims(claims, qualifiers, references)
	c = claims['preferred'] or {}
	if qualifiers then
		q = qualifiers['preferred'] or {}
	end
	if references then
		r = references['preferred'] or {}
	end
		
	if claims and claims['normal'] then
		for i, value in ipairs(claims['normal']) do
			c[#c + 1] = value
			if qualifiers then
				q[#q + 1] = qualifiers['normal'][i]
			end
			if references then
				r[#r + 1] = references['normal'][i]
			end
		end
	end
	if claims and claims['deprecated'] then
		for i, value in ipairs(claims['deprecated']) do
			c[#c + 1] = value
			if qualifiers then
				q[#q + 1] = qualifiers['deprecated'][i]
			end
			if references then
				r[#r + 1] = references['deprecated'][i]
			end
		end
	end
	return c, q, r
end



-- Use only if the data type of the property is time
function p.targetTime(item, property)
    if item and item.claims and item.claims[property] then
     for _,claim in pairs(item.claims[property]) do
       if claim.mainsnak and claim.mainsnak.datavalue then
          return claim.mainsnak.datavalue.value.time
       end
     end
    end
end


-- same as targetId but for qualifiers
-- TODO merge
function p.qualifierTargetId(qualifiers, property)
    local claims = {}
    if qualifiers and qualifiers[property] then
        for _,claim in pairs(qualifiers[property]) do
            local valueid = claim.datavalue.value['numeric-id']
            table.insert(claims, valueid)
        end
    end
    return claims
end

-- same as targetTime but for qualifiers
-- TODO merge
function p.qualifierTargetTime(qualifiers, property)
    local claims = {}
    if qualifiers and qualifiers[property] then
    	for _,claim in pairs(qualifiers[property]) do
     		if claim.datavalue then
     			mw.log('time qualifier', property, claim.datavalue.value.time)
        		return claim.datavalue.value.time
    		end
    	end
    end
end

-- takes a list of item ids (the values of the given table) and creates wikilinks based on their labels
function p.createLinks(list, authorAbbreviation)
    local authors = {}
    for _,authorid in pairs(list) do
        if authorid then
            local author = mw.wikibase.getEntityObject('Q' .. authorid)
            if author then
                local label = p.getItemLabel(author)
                if authorAbbreviation then
                    if p.icnafpApplies() then
                        if p.targetStr(author, 'P428') then -- P428 author citation per IPNI set
                            label = p.targetStr(author, 'P428')
                        end
                    elseif p.targetStr(author, 'P835') then -- P835 author citation set
                        label = p.targetStr(author, 'P835')
                    elseif label and label ~= 'no label' then
                        for word in mw.ustring.gmatch(label, "%w+") do
                            label = word
                        end
                    end
                end
                table.insert(authors, '[[Q' .. tostring(authorid) .. '|' .. label .. ']]')
            end
        end
    end
    return authors
end

function p.map(item)
	local map = p.targetStr(item, 'P181')
    if map then
    	return mw.text.tag( 'tr', {}, mw.text.tag( 'th', { colspan='2', style='text-align: center; background-color: ' .. colors[code] }, 'range map')) .. mw.text.tag( 'tr', {}, mw.text.tag( 'td', {colspan='2'}, '[[File:' .. map .. '|220px|.]]'))
    end
    
    return ''
end

function p.iucn(item)
	local statusid, statusreferences = next(p.targetId(item, 'P141')) -- IUCN conservation status
    if statusid then
    	local status = mw.wikibase.getEntityObject('Q' .. statusid)
    	local img, imgqualifiers, imgreferences = p.targetStr(status, 'P18') -- image
    	if img then
    		local refstr = p.references(statusreferences)
    		return mw.text.tag( 'tr', {}, mw.text.tag( 'th', { colspan='2', style='text-align: center; background-color: ' .. colors[code] }, '[[Q32059|red list status]]' .. refstr)) .. mw.text.tag( 'tr', {}, mw.text.tag( 'td', {colspan='2'}, '[[File:' .. img .. '|220px|' .. p.getItemLabel(status) .. ']]'))
    	end
    end
    
    return ''
end

-- shows the audio file
function p.audio(item)
	local audio, audioreferences = p.targetStr(item, 'P51') -- audio file
    if audio then
    	return mw.text.tag( 'tr', {}, mw.text.tag( 'th', { colspan='2', style='text-align: center; background-color: ' .. colors[code] }, '[[Property:P51|audio]]')) .. mw.text.tag( 'tr', {}, mw.text.tag( 'td', {colspan='2'}, '[[File:' .. audio .. ']]'))
    end
    
    return ''
end

-- returns html for the given refids set
-- parameters:
-- refids: list of integer ID to create a list of <ref>-references
function p.references(refids)
    local frame = mw.getCurrentFrame()
    local refstr = ''
    if refids then
        for id,_ in pairs(refids) do
            --local ref = Cite.citeitem(id, lang or 'en') or 'Error during creation of citation. Please report [[' .. id .. ']] at [[Module_talk:Cite]]'
            mw.log('refstr for ', id, ref)
            refstr = refstr .. frame:extensionTag('ref', ref, {name=id})
        end
    end
    return refstr
end

return p