Modul:Wikidata/TreeView
Vzhled
Dokumentaci tohoto modulu lze vytvořit na stránce Nápověda:Modul:Wikidata/TreeView
-- Tato stránka je pravidelně aktualizována robotem. Jakákoliv modifikace bude při příští aktualizaci přepsána a je třeba ji provádět na Wikipedii.
local p = {}
local wikidata = require('Modul:Wikidata')
-- from Module:Wikidata from wikidata https://www.wikidata.org/wiki/Module:Wikidata
function p.bestRanked(claims)
if not claims then
return nil
end
local preferred, normal = {}, {}
for _, j in ipairs(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
function p.isSpecial(snak)
return snak.snaktype ~= 'value'
end
function p.getEntity( val )
if type(val) == 'table' then
return val
end
return mw.wikibase.getEntityObject(val)
end
function p.removeBlanks(args)
for i, j in pairs(args) do -- does not work ??
if (j == '') or (j == '-') then args[i] = nil end
end
return args
end
function p.getClaims( args ) -- returns a table of the claims matching some conditions given in args
args = p.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 = p.getEntity(args.entity)
local property = string.upper(args.property)
if not entity or not entity.claims or not entity.claims[property] 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.claims[property]) do
if
(
not args.excludespecial
or
not (p.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 = p.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
-- generator, see http://lua-users.org/wiki/LuaCoroutinesVersusPythonGenerators for lua doc
-- definition a function that when called several times will generate a sequence of strings
-- gensymbols = create_gensymbols("ABC")
-- gensymbols() == "A"
-- gensymbols() == "B" g
-- gensymbols() == "C"
-- gensymbols() == "AA" '
-- gensymbols() == "BABBA"
-- ...
function p.gensymbols(chars)
local i = 0
local charset = chars
local generator = function ()
local symbol = ""
local rest
rest = i
repeat
local j
j = rest % string.len(charset)
rest = math.floor(rest / string.len(charset))
symbol = symbol .. string.sub(charset, j+1, j+1)
until rest <= 0
i = i + 1
return symbol
end
return generator
end
---------------------------------------------------
--Only support items
function p.getPageName( id )
if type( id ) == 'table' then
return 'Q' .. id[2]
else
return id:upper()
end
end
function p.children(query, itemId)
mw.log(itemId)
local childrens = {}
local x = 1
query.item = itemId
local claims = p.getClaims(query)
if not claims then
return {}
end
for _,claim in pairs( claims ) do
local value = claim.mainsnak.datavalue.value
local nextitem = 'Q' .. value['numeric-id']
childrens[x] = nextitem
x = x + 1
end
return childrens
end
function p.outputTree( query, firstItemId, maxdepth, itemOutput, lang )
----- topological sort and meta data of the DAG ( https://en.wikipedia.org/wiki/Topological_sorting )
local function firstPass( query, firstItemId, item_repr )
--while there are unmarked nodes do
-- select an unmarked node n
-- visit(n)
--function visit(node n)
-- if n has a temporary mark then stop (not a DAG)
-- if n is not marked (i.e. has not been visited yet) then
-- mark n temporarily
-- for each node m with an edge from n to m do
-- visit(m)
-- mark n permanently
-- add n to head of L
local firstItem = mw.wikibase.getEntityObject(firstItemId)
local content = itemOutput( firstItem )
local opened = 1
local closed = 2
local incomplete = 3
local marks = {}
local datas = {}
local childrens = {}
local function setdata(n, key, val)
if datas[n] == nil then
datas[n] = {}
end
datas[n][key] = val
end
local function getdata(n, key, defval)
if datas[n] == nil or datas[n][key] == nil then
return defval
else
return datas[n][key]
end
end
-- this function
-- * visits and builds the tree, DAG or graph,
-- * in the same pass, computes a topological ordering for the DAG
-- * annotates the nodes with informations
local function visit(n, depth, rank)
mw.log( depth .. ">=" .. maxdepth)
mw.log(n .. " " .. tostring(marks[n]))
if marks[n] == opened then
setdata(n, "status", "loop")
setdata(n, "rank", rank)
return rank
elseif marks[n] ~= closed then
marks[n] = opened
childrens[n] = p.children(query, n)
for _, node in ipairs(childrens[n]) do
setdata(node, "nparents",
getdata(node, "nparents", 0) + 1
)
if depth <= maxdepth then
rank = visit(node, depth + 1, rank + 1)
end
end
if depth <= maxdepth then
if getdata(n, "status", "complete") ~= "loop" then
setdata(n, "status", "complete")
end
marks[n] = closed
else
setdata(n, "status", "incomplete")
marks[n] = incomplete
end
setdata(n, "rank", rank)
end
return rank + 1
end
setdata(firstItemId, "nparents", 0)
visit(firstItemId, 1, 0)
return datas, childrens
end
local langobj = mw.language.new(lang)
-- link inside tree
local function formatSymbol(prefix)
return '<span class="Unicode"><small>(' .. prefix .. ")</small></span>"
end
local function genAnchor(id)
return '<span id="' .. firstItemId .. id .. '"></span>'
end
local function anchorLink(id, content)
return "[[#" .. firstItemId .. id .. "|" .. content .. "]]"
end
local function fmtTreeLinks(content)
return mw.text.tag("sup", {}, content)
end
local function renderTree( itemId, datas, children, itemOutput, alreadyOuted, gs )
local item = mw.wikibase.getEntityObject(itemId)
local content = itemOutput( item )
local state
if datas[itemId]["status"] ~= nil then
state = datas[itemId]["status"]
end
mw.log(itemId .. " " .. state )
if datas[itemId] ["nparents"] > 1 and datas[itemId]["symbol"] == nil then
setdata(itemId, "symbol", gs() )
end
-- prevent infinite loops in non DAGs
if state == "loop" then
if datas[itemId]["looped"] == nil then
datas[itemId]["looped"] = "treated"
content = fmtTreeLinks(" ∞") .. " " .. content .. genAnchor(itemId)
else
return content .. fmtTreeLinks("∞" .. " ↑ " .. anchorLink(itemId, formatSymbol( datas[itemId]["symbol"] )))
end
elseif state == "incomplete" or state == "unvisited" then
content = content .. " " .. fmtTreeLinks("…")
return content
end
-- has no chilren ? display as leaf
if children[itemId] ~= nil and table.getn(children[itemId]) == 0 then
return " " .. content .. " • " -- would be great to use "🍁", but font problem
end
datas[itemId] ["nparents"] = datas[itemId]["nparents"] - 1
local parts = {}
-- sort children topologycally
if alreadyOuted[itemId] == nil then
local order = children[itemId]
table.sort(order, function (a, b) return datas[a]["rank"] < datas[b]["rank"] end )
for i, childId in ipairs(order) do
table.insert( parts, renderTree( childId, datas, children, itemOutput , alreadyOuted, gs ) )
end
local l = table.maxn( parts )
for i = 1,(l - 1) do
parts[i] = mw.text.tag( 'li', {}, parts[i] )
end
parts[l] = mw.text.tag( 'li', { class = 'lastline' }, parts[l] )
local langobj = mw.language.new(lang)
local prefix = " "
if datas[itemId] ["nparents"] > 0 then
local arrow = langobj:getArrow()
prefix = fmtTreeLinks(genAnchor(itemId) .. " " .. arrow .. " " .. formatSymbol(datas[itemId]["symbol"]))
end
alreadyOuted[itemId] = prefix .. " " .. content .. mw.text.tag( 'ul', {}, table.concat( parts ) )
end
if datas[itemId] ["nparents"] <= 0 or state == "loop" then
return alreadyOuted[itemId]
else
return content .. " " .. fmtTreeLinks(anchorLink(itemId, formatSymbol(datas[itemId]["symbol"])) .. " " .. langobj:getArrow(forward))
end
end
local gen
-- gen = p.gensymbols("⦿⦾")
-- gen = p.gensymbols("12")
-- gen = p.gensymbols("★☆✴")
gen = p.gensymbols("@*#")
local datas
local children
datas, children = firstPass( query, firstItemId, gen)
local alreadyOuted = {}
local rendering = {}
return renderTree( firstItemId, datas, children, itemOutput, alreadyOuted, gen)
end
function p.outputForest( query, firstItemsId, maxdepth, itemOutput, lang )
local content = ''
mw.log(maxdepth)
for _, item in pairs( firstItemsId ) do
content = content .. mw.text.tag( 'li', {}, p.outputTree( query, item, maxdepth, itemOutput, lang ) )
end
local res = mw.text.tag( 'div', { class = 'treeview' }, mw.text.tag( 'ul', {}, content ) )
return res
-- return ""
end
function p._outputTree(query, firstItemsId, maxdepth, lang, formatting)
if not maxdepth then
maxdepth = 10
end
query.excludespecial = true
if tonumber(query.property) then --legacy format
query.property = 'P'.. tostring(query.property)
end
local itemOutput
if formatting and type(formatting) == 'function' then
itemOutput = function( item ) return formatting(item, lang) end
elseif formatting and type(formatting) == 'table' then
itemOutput = function(item) return mw.create.html('span'):css(formatting):wikitext(wikidata.formatEntity(item,{lang=lang})):done() end
else
itemOutput = function(item) return wikidata.formatEntity(item, {lang=lang}) end
end
return p.outputForest( query, firstItemsId, maxdepth, itemOutput, lang )
end
function p._familytree(item, lang, maxdepth)
if not maxdepth then maxdepth = 3 end
local birthvals = {}
return p._outputTree( --query, firstItemsId, maxdepth, lang, formatting
{ -- Define query
property = 'P40', -- Property:Child
sorttype = function(A, B) -- sort by birth Date
-- this is *memory* intensive there should be a way to desactive sorting above some number of nodes
local personA, personB = wikidata.getmainid(A), wikidata.getmainid(B)
local birthA, birthB = wikidata.getDate(personA), wikidata.getDate(personB)
return wikidata.comparedate(birthA, birthB)
end
},
{item},
maxdepth,
lang,
function(item) -- Define displayformat
local val = wikidata.formatEntity(item, {lang=lang})
local bgcolor = '#888888'
local gender = wikidata._formatStatements{entity = item, property = 'P21', numval = 1, displayformat='raw', lang = lang}
if gender == 'Q6581097' then
bgcolor = '#006699'
elseif gender == 'Q6581072' then
bgcolor = '#990000'
end
if item.claims and item.claims.P569 and item.claims.P569[1].mainsnak.snaktype == 'value' then
birthyear = item.claims.P569[1].mainsnak.datavalue.value
end
if item.claims and item.claims.P570 and item.claims.P570[1].mainsnak.snaktype == 'value' then
deathyear = item.claims.P570[1].mainsnak.datavalue.value
end
if birthyear or deathyear then
local daterange = require('Module:Daterange').compactdaterange({startpoint = birthyear, endpoint = deathyear}, lang)
val = val .. ' ' .. require('Module:Linguistic').inparentheses(daterange,lang)
end
local desc = wikidata._formatStatements({entity = item, property = {'P39', 'P106'} , lang=lang}) or ''
val = val .. ' <small>' .. desc .. '</small>'
local val = mw.html.create('span')
:css({ ['border-style'] = solid, ['border-width'] = medium}) --, ['background-color'] = bgcolor, ['color'] = '#FFF'
:wikitext(val)
:done()
return tostring(val)
end)
end
function p.outputTreeFromTemplate( frame )
local frame = frame:getParent()
local query = frame.args
local firstItemsId = mw.text.split( frame.args.items, ' ' )
if table.maxn( firstItemsId ) == 0 then
return 'No items passed as parameter'
end
local maxdepth, lang
if frame.args.recursion and frame.args.recursion ~= '' then
maxdepth = tonumber( frame.args.recursion )
end
if frame.args.lang and frame.args.lang ~= ''then
lang = frame.args.lang
else
lang = frame:preprocess('{{int:lang}}')
end
return p._outputTree(query, firstItemsId, maxdepth, lang)
end
function p.outputTreeFromTemplateWithInstance( frame )
local frame = frame:getParent()
local query = frame.args
local firstItemsId = mw.text.split( frame.args.items, ' ' )
local item = frame.args[1]
if table.maxn( firstItemsId ) == 0 then
return 'No items passed as parameter'
end
local maxdepth, lang
if frame.args.recursion and frame.args.recursion ~= '' then
maxdepth = tonumber( frame.args.recursion )
end
if frame.args.lang and frame.args.lang ~= ''then
lang = frame.args.lang
else
lang = frame:preprocess('{{int:lang}}')
end
return p._outputTree(query, firstItemsId, maxdepth, lang,function(item) -- Define displayformat
local val = wikidata.formatEntity(item, {lang=lang})
local optionsStatements = {}
optionsStatements.property = "P31"
optionsStatements.id = item.id
--local optionsInstance = {}
--optionsInstance.property='P31'
--optionsInstance.of='P31'
--optionsInstance.id=item['id']
--local instance = wikidata._formatStatements({entity = item, property = {'P31'} , lang=lang}) or ''
--local statements = wikidata.getStatements(optionsStatements)
--optionsInstance.statements = statements
local instance = wikidata.formatStatementsFromLua(optionsStatements)
if instance == nil then
instance = ''
end
--mw.logObject(statements)
--local instance = item['id']
mw.logObject(instance)
val = val .. ' (<small>' .. instance .. '</small>)'
return val
end)
end
function p.familytree(frame)
local lang = frame.args.lang
if not lang or lang == '' then
lang = frame:preprocess('{{int:lang}}')
end
return p._familytree(frame.args[1], lang, tonumber(frame.args.maxdepth))
end
return p