Module:Navigation by Wikidata/sandbox
Jump to navigation
Jump to search
Lua
CodeDiscussionEditHistoryLinksLink count Subpages:DocumentationTestsResultsSandboxLive code All modules
no dokucemtation yet.
Code
-- =============================================================================
-- Routines for the automatic generation of simple navigation boxes and complete
-- category description pages with navigation, using Wikidata
-- =============================================================================
local arguments = require "Module:Arguments"
local navbox = require "Module:Navbox"
local wikidata = require "Module:Wikidata label"
local p = {}
-- =============================================================================
-- Helper functions for Wikidata access
-- =============================================================================
-- -----------------------------------------------------------------------------
-- Return a single claim for a property, throw error if more than one claim
-- -----------------------------------------------------------------------------
local function getOneClaim(entityId, propertyId)
local claims = mw.wikibase.getBestStatements(entityId, propertyId)
if #claims > 0 then
assert(#claims == 1, entityId .. " has more than one claim for " .. propertyId)
return claims[1]
else
return nil
end
end
-- -----------------------------------------------------------------------------
-- Return information about a claim, usable for error messages
-- -----------------------------------------------------------------------------
local function claimInfo(claim)
return (
"Item " .. string.match(claim.id, "^(.*)%$")
.. ", Property " .. claim.mainsnak.property
)
end
-- -----------------------------------------------------------------------------
-- Return the item id for a claim that points to a data item
-- -----------------------------------------------------------------------------
local function getAnyId(claim, datatype, entitytype)
assert(
claim.mainsnak.datatype == datatype,
claimInfo(claim) .. " is not of datatype " .. datatype
)
local datavalue = assert(
claim.mainsnak.datavalue,
claimInfo(claim) .. " has no datavalue"
)
assert(
datavalue.type == "wikibase-entityid",
claimInfo(claim) .. "'s value is not of type wikibase-entityid"
)
local value = assert(
datavalue.value,
claimInfo(claim) .. " has no value"
)
assert(
value["entity-type"] == entitytype,
claimInfo(claim) .. "'s value's entity-type is not " .. entitytype
)
return assert(
value.id,
claimInfo(claim) .. "'s value has no id field"
)
end
-- -----------------------------------------------------------------------------
-- Return the item id for a claim that points to a data item
-- -----------------------------------------------------------------------------
local function getItemId(claim)
return getAnyId(claim, "wikibase-item", "item")
end
-- -----------------------------------------------------------------------------
-- Return the property id for a claim that points to a property
-- -----------------------------------------------------------------------------
local function getPropertyId(claim)
return getAnyId(claim, "wikibase-property", "property")
end
-- =============================================================================
-- Helper functions for connecting Wikidata and Commons
-- =============================================================================
-- -----------------------------------------------------------------------------
-- Return a link to the Commons category page for a Wikidata item, using a pattern
-- -----------------------------------------------------------------------------
local function getLink(itemId, lowercase, pattern, redlink)
-- Find out the Commons category name for the data item
local claim = assert(
getOneClaim(itemId, "P373"),
itemId .. " has no property P373"
)
-- Determine the value to use as a pattern replacement
local value = mw.wikibase.renderSnak(claim.mainsnak)
if lowercase then
value = value:gsub("^%u", string.lower)
end
-- Substitute it into the pattern
local target = ":Category:" .. string.gsub(
pattern,
"<<[^>]+>>",
value)
-- Check if it would be a red link
if not (redlink or mw.title.new(target).exists) then
return nil
end
-- Return the link in wikitext
return "[[" .. target .. "|" .. mw.wikibase.getLabel(itemId) .. "]]"
end
-- -----------------------------------------------------------------------------
-- Return the Wikidata item id for the base item
-- -----------------------------------------------------------------------------
local function getBaseItemId(itemId, propertyId, level)
if level == "children" then
-- For children, start at exactly this item
return itemId
elseif level == "siblings" then
-- For siblings, start at the parent item
local parentClaim = getOneClaim(propertyId, "P1696")
if not parentClaim then
return nil
end
local parentPropertyId = getPropertyId(parentClaim)
if not parentPropertyId then
return nil
end
local claim = getOneClaim(itemId, parentPropertyId)
if not claim then
return nil
end
return getItemId(claim)
else
error("Invalid level “" .. tostring(level) .. "”")
end
end
-- =============================================================================
-- Sort functions
-- =============================================================================
local sortfunc = {}
-- -----------------------------------------------------------------------------
-- By label
-- -----------------------------------------------------------------------------
function sortfunc.label(a, b)
return (mw.wikibase.getLabel(a) or '') < (mw.wikibase.getLabel(b) or '')
end
-- =============================================================================
-- Functions to build navigation blocks
-- =============================================================================
-- -----------------------------------------------------------------------------
-- Determine Wikidata item ID for the navigation title
-- Arguments: title, item, property, level
-- -----------------------------------------------------------------------------
local function getTitleItemId(args)
if args.title then
return args.title
elseif args.item and args.property and args.level then
return getBaseItemId(args.item, args.property, args.level)
end
end
-- -----------------------------------------------------------------------------
-- Build navigation title
-- Arguments: title, item, property, level, pattern
-- -----------------------------------------------------------------------------
local function getTitle(args)
local titleItemId = getTitleItemId(args)
if not titleItemId then
return nil
end
-- Instead of a red link, return just the label without any link
return getLink(titleItemId, false, args.pattern, false) or mw.wikibase.getLabel(titleItemId)
end
-- -----------------------------------------------------------------------------
-- Build navigation list
-- Arguments: item, property, level, sort, lowercase, pattern, redlinks, [itemId...]
-- -----------------------------------------------------------------------------
function p._navigationList(args)
-- Start with the Wikidata item IDs given as array arguments
local itemIdList = {}
for i, value in ipairs(args) do
table.insert(itemIdList, value)
end
-- Add items from the Wikidata statements
if args.item and args.property and args.level then
-- Find out which Wikidata item to use as the starting point
local baseItemId = getBaseItemId(args.item, args.property, args.level)
if not baseItemId then
return nil
end
-- Add the Wikidata item IDs from the Wikidata statements to our list
for k, claim in pairs(mw.wikibase.getBestStatements(baseItemId, args.property)) do
table.insert(itemIdList, (getItemId(claim)))
end
end
-- Sort as requested
if args.sort then
table.sort(itemIdList, sortfunc[args.sort])
end
-- Build wikitext output
local result = ""
for i, itemId in ipairs(itemIdList) do
local x = getLink(itemId, args.lowercase, args.pattern, args.redlinks)
if x then
result = result .. "* " .. x .. "\n"
end
end
return result
end
-- -----------------------------------------------------------------------------
-- Build a complete navigation block with title and item list
-- Arguments: title, item, property, level, sort, lowercase, pattern, redlinks, [ItemId...]
-- -----------------------------------------------------------------------------
function p._navigationBlock(args)
-- If level is not defined, do both siblings and children
if args.item and args.property and not args.level then
local result = ""
args.level = "siblings"
result = result .. (p._navigationBlock(args) or "")
args.level = "children"
result = result .. (p._navigationBlock(args) or "")
return result
end
-- Check if the list would only have red links
local redlinks = args.redlinks
args.redlinks = false
local existing = p._navigationList(args)
args.redlinks = redlinks
if not existing or existing == "" then
return nil
end
-- Now build the real navigation data
local title = getTitle(args)
local list = p._navigationList(args)
if not (title and list) then
return nil
end
return "; " .. title .. "\n" .. list
end
-- -----------------------------------------------------------------------------
-- Build a stand-alone navigation box
-- Arguments: title, item, property, level, sort, lowercase, pattern, redlinks, [ItemId...]
-- -----------------------------------------------------------------------------
function p._navigationBox(args)
-- Find out flag to display, if any
local flag
local titleItemId = getTitleItemId(args)
if titleItemId then
local flagClaim = getOneClaim(titleItemId, "P41")
if flagClaim then
flag = "[[File:" .. flagClaim.mainsnak.datavalue.value .. "|30px|border]]"
end
end
return navbox._navbox{
name = args.name,
title = getTitle(args),
above = args.above,
imageleftstyle = "width: 1px;", -- Workaround for a bug in Module:Navbox that breaks formatting on Chrome
imageleft = flag,
listclass = "hlist",
liststyle = "width: auto;", -- Workaround for a bug in Module:Navbox that breaks formatting on Chrome
list1 = p._navigationList(args),
below = args.below
}
end
-- -----------------------------------------------------------------------------
-- Build an embeddable, borderless navigation box
-- Arguments: title, item, property, level, sort, lowercase, pattern, redlinks, [ItemId...]
-- -----------------------------------------------------------------------------
function p._navigationInline(args)
return navbox._navbox{
border = "none",
bodystyle = "background-color:transparent;",
listclass = "hlist",
list1 = p._navigationList(args)
}
end
-- =============================================================================
-- Functions to build complete category description boxes
-- =============================================================================
-- -----------------------------------------------------------------------------
-- Escape magic characters for a regular expression
-- -----------------------------------------------------------------------------
local function regExEscape(pattern)
return string.gsub(pattern, "([%(%)%.%%%+%-%*%?%[%^%$])", "%%%1")
end
-- -----------------------------------------------------------------------------
-- Helper function to convert a pattern as given in the parameter into a
-- Lua-style regular expression
-- -----------------------------------------------------------------------------
local function makeRegEx(pattern, variable)
-- Change underlines into spaces
local regex = string.gsub(pattern, "_", " ")
-- Escape any magic characters in the pattern
regex = regExEscape(regex)
-- Change <<...>> into regex placeholders
regex = string.gsub(regex, "<<" .. variable .. ">>", "(.*)")
regex = string.gsub(regex, "<<.*>>", ".*")
-- Add beginning and end marks
regex = "^" .. regex .. "$"
return regex
end
-- -----------------------------------------------------------------------------
-- Determine navigation data for a page and a pattern
-- -----------------------------------------------------------------------------
local function getNavigationBlocks(pagename, pattern, title)
if not pagename then
return title, ""
end
local frame = mw.getCurrentFrame()
local blocks = ""
for variable in string.gmatch(pattern, "<<([^>]+)>>") do
-- Determine variable content in current page name
local content = assert(
string.match(pagename,makeRegEx(pattern, variable)),
"“" .. pagename .. "” does not match “" .. pattern .. "”"
)
-- Find out which wikidata entity matches the variable content
local CategoryItemId = assert(
mw.wikibase.getEntityIdForTitle('Category:' .. content),
"Page “Category:" .. content .. "” not found"
)
local claim = assert(
getOneClaim(CategoryItemId, "P301"),
CategoryItemId .. " has no property P301"
)
local itemId = getItemId(claim)
-- Split the variable at each ":" to separate modifiers
local navigationArgs = {
style = "block",
item = itemId
}
local template = nil
for part in string.gmatch(variable, "[^:]+") do
if not template then
template = part
elseif part == "redlinks" then
navigationArgs.redlinks = "yes"
elseif part == "noredlinks" then
navigationArgs.redlinks = ""
elseif part == "nochildren" then
navigationArgs.level = "siblings"
end
end
-- Find out the navigation pattern for this variable
navigationArgs.pattern = string.gsub(
pagename,
regExEscape(content),
"<<" .. template .. ">>"
)
-- Call the right template
local block = frame:expandTemplate{
title = "Navigation by/" .. template,
args = navigationArgs
}
if title ~= "" then
title = title .. " - "
end
local lang = frame:callParserFunction( "int", "lang" ) -- get user's chosen language
title = title .. wikidata._getLabel(itemId, lang, "wikipedia")
blocks = blocks .. block .. "\n"
end
return title, blocks
end
-- -----------------------------------------------------------------------------
-- Build a box with description and navigation blocks
-- Arguments: name, description, remarks, pattern, pagename
-- -----------------------------------------------------------------------------
function p._categoryDescription(args)
local boxargs = {
name = args.name,
above = args.description,
listclass = "hlist",
below = args.remarks
}
local blocks
boxargs.title, blocks = getNavigationBlocks(
args.pagename,
args.pattern or "",
args.title or ""
)
local count = 0
for start, line in string.gmatch(blocks, "([%*;]) *([^\n]+)") do
if start == ";" then
count = count + 1
boxargs["group" .. tostring(count)] = line
boxargs["list" .. tostring(count)] = ""
elseif count > 0 then
boxargs["list" .. tostring(count)] = (
boxargs["list" .. tostring(count)]
.. "* " .. line .. "\n"
)
end
end
return navbox._navbox(boxargs)
end
-- =============================================================================
-- Function wrappers for usage with #invoke
-- =============================================================================
function p.navigationList(frame)
return p._navigationList(arguments.getArgs(frame))
end
function p.navigationBlock(frame)
return p._navigationBlock(arguments.getArgs(frame))
end
function p.navigationBox(frame)
return p._navigationBox(arguments.getArgs(frame))
end
function p.navigationInline(frame)
return p._navigationInline(arguments.getArgs(frame))
end
function p.categoryDescription(frame)
return p._categoryDescription(arguments.getArgs(frame))
end
-- =============================================================================
return p