48,407
edits
No edit summary |
No edit summary |
||
| Line 1: | Line 1: | ||
--[=[ | --[=[ | ||
Authors: [[ | Authors: [[User:kc_kennylau]], [[User:JohnC5]], [[User:Erutuon]], [[User:Suzukaze-c]], [[User:Theknightwho]], [[User:AryamanA]] | ||
--]=] | --]=] | ||
local | local export = {} | ||
local regular_languages = require("Module:languages/ | local regular_languages = require("Module:languages/code to canonical name") | ||
local families = require("Module:families/ | local etymology_languages = require("Module:etymology languages/code to canonical name") | ||
local families = require("Module:families/code to canonical name") | |||
function export.find_subtree(t, code) | |||
function | |||
for _, val in ipairs(t) do | for _, val in ipairs(t) do | ||
if val.name == code then -- "name" is really code | if val.name == code then -- "name" is really code | ||
return { val } | return {val} | ||
end | |||
local result = export.find_subtree(val, code) | |||
if result ~= nil then | |||
return result | |||
end | end | ||
end | end | ||
| Line 31: | Line 24: | ||
local family_icon = "F" | local family_icon = "F" | ||
local | local variety_icon = "V" | ||
local proto_language_icon = family_icon | local proto_language_icon = family_icon | ||
local family_with_proto_language_icon = family_icon | local family_with_proto_language_icon = family_icon | ||
local function format_node(code, is_protolanguage_or_has_protolanguage) | local function format_node(code, is_protolanguage_or_has_protolanguage, options) | ||
local canonical_name, category_name, class, icon, tooltip | local canonical_name, category_name, class, icon, tooltip, lemma_count_text | ||
if regular_languages[code] then | if regular_languages[code] then | ||
canonical_name = regular_languages[code | canonical_name = regular_languages[code] | ||
category_name = canonical_name .. | category_name = canonical_name:match(" [Ll]anguage$") and canonical_name or canonical_name .. " language" | ||
class = "familytree-lang" | class = "familytree-lang" | ||
if is_protolanguage_or_has_protolanguage then | if is_protolanguage_or_has_protolanguage then | ||
class = class .. ' familytree-protolang' | class = class .. ' familytree-protolang' | ||
icon = proto_language_icon | icon = proto_language_icon | ||
end | |||
-- Add lemma count if the lemma_count option is set and category name exists | |||
if options and options.lemma_count and category_name then | |||
page_count = mw.site.stats.pagesInCategory( | |||
canonical_name .. " lemmas", | |||
"pages" | |||
) | |||
lemma_count_text = ' (' .. page_count .. ')' | |||
end | end | ||
elseif etymology_languages[code] then | elseif etymology_languages[code] then | ||
canonical_name = etymology_languages[code] | canonical_name = etymology_languages[code] | ||
class = "familytree-etymlang" | class = "familytree-etymlang" | ||
icon = | icon = variety_icon | ||
tooltip = " | tooltip = "Variety" | ||
elseif families[code] then | elseif families[code] then | ||
canonical_name = families[code] | canonical_name = families[code] | ||
category_name = canonical_name .. " languages" | category_name = (canonical_name:match(" [Ll]anguages$") or canonical_name:match(" [Ll]ects$")) and canonical_name or | ||
canonical_name .. " languages" | |||
class = "familytree-family" | class = "familytree-family" | ||
if is_protolanguage_or_has_protolanguage then | if is_protolanguage_or_has_protolanguage then | ||
| Line 68: | Line 71: | ||
.. ' <span class="familytree-code">(' .. code .. ')</span>]]' | .. ' <span class="familytree-code">(' .. code .. ')</span>]]' | ||
.. (icon and ' <span class="familytree-icon">' .. icon .. '</span>' or '') | .. (icon and ' <span class="familytree-icon">' .. icon .. '</span>' or '') | ||
-- Include lemma count text if available | |||
.. (lemma_count_text or '') | |||
.. '</span>' | .. '</span>' | ||
end | end | ||
| Line 118: | Line 123: | ||
end | end | ||
local function make_node(code, is_protolanguage, protolanguage_of) | local function make_node(code, is_protolanguage, protolanguage_of, options) | ||
return '</span> ' .. format_node(code, | return '</span> ' .. format_node(code, | ||
is_protolanguage[code] or protolanguage_of[code] ~= nil) | is_protolanguage[code] or protolanguage_of[code] ~= nil, options) | ||
end | end | ||
| Line 129: | Line 134: | ||
end | end | ||
export.are_all_children_etymology_languages = require("Module:memoize")(function (nested_data) | |||
if not nested_data[1] then | if not nested_data[1] then | ||
return nil | return nil | ||
| Line 136: | Line 141: | ||
for _, child in ipairs(nested_data) do | for _, child in ipairs(nested_data) do | ||
if not etymology_languages[child.name] | if not etymology_languages[child.name] | ||
or | or export.are_all_children_etymology_languages(child) == false then | ||
return false | return false | ||
end | end | ||
| Line 151: | Line 156: | ||
end | end | ||
local no_break_space = | local no_break_space = "\194\160" | ||
local level_separator = (no_break_space):rep(3) | local level_separator = (no_break_space):rep(3) | ||
local expandtext, collapsetext = "[+]─", "[-]┬" | local expandtext, collapsetext = "[+]─", "[-]┬" | ||
local function make_tree(data, is_protolanguage, protolanguage_of, options, prefix) | local function make_tree(data, is_protolanguage, protolanguage_of, options, prefix) | ||
local result = | local result = {} | ||
local function ins(val) | |||
table.insert(result, val) | |||
end | |||
-- This tag is closed in the node generated by make_node. | -- This tag is closed in the node generated by make_node. | ||
| Line 171: | Line 179: | ||
local code = val.name | local code = val.name | ||
local language_or_family_node = | local language_or_family_node = | ||
make_node(code, is_protolanguage, protolanguage_of) | make_node(code, is_protolanguage, protolanguage_of, options) | ||
if not val[1] then | if not val[1] then | ||
ins('<li>' .. prefix .. branch .. options.sterile_branch_text | |||
.. language_or_family_node .. '</li>') | .. language_or_family_node .. '</li>') | ||
else | else | ||
local customcollapsible_id = get_customcollapsible_id() | local customcollapsible_id = get_customcollapsible_id() | ||
ins('<li>' .. prefix .. branch | |||
.. '<span class="familytree-toggle mw-customtoggle-' | .. '<span class="familytree-toggle mw-customtoggle-' | ||
.. customcollapsible_id .. '">───┬</span>') | .. customcollapsible_id .. '">───┬</span>') | ||
| Line 193: | Line 201: | ||
val = val[1] | val = val[1] | ||
top_node = make_node(code, is_protolanguage, protolanguage_of) | top_node = make_node(code, is_protolanguage, protolanguage_of, options) | ||
if options.protolanguage_under_family then | if options.protolanguage_under_family then | ||
top_node, language_or_family_node = | top_node, language_or_family_node = | ||
| Line 201: | Line 209: | ||
local all_children_are_etymology_languages = | local all_children_are_etymology_languages = | ||
export.are_all_children_etymology_languages(val) | |||
local collapsible_ul = '<ul class="mw-collapsible' | local collapsible_ul = '<ul class="mw-collapsible' | ||
| Line 212: | Line 220: | ||
if flag then | if flag then | ||
ins(top_node | |||
.. collapsible_ul .. '<li>' .. prefix | .. collapsible_ul .. '<li>' .. prefix | ||
.. (i == length and no_break_space or "│") | .. (i == length and no_break_space or "│") | ||
| Line 218: | Line 226: | ||
end | end | ||
ins(language_or_family_node) | |||
if not flag then | if not flag then | ||
ins(collapsible_ul) | |||
end | end | ||
| Line 227: | Line 235: | ||
-- and data-collapsetext attribute values to the custom toggle, | -- and data-collapsetext attribute values to the custom toggle, | ||
-- so have to have a custom script do it. | -- so have to have a custom script do it. | ||
ins(make_tree(val, is_protolanguage, protolanguage_of, options, next_level)) | |||
ins('</ul></li>') | |||
end | end | ||
end | end | ||
return | return table.concat(result) | ||
end | end | ||
| Line 251: | Line 259: | ||
end | end | ||
function | function export.show(frame) | ||
local args = frame.args | local args = frame.args | ||
local descendants_of = args[1] | local descendants_of = args[1] | ||
local to_boolean = require("Module:yesno") | local to_boolean = require("Module:yesno") | ||
| Line 269: | Line 271: | ||
-- Determines whether all etymology languages will be shown. | -- Determines whether all etymology languages will be shown. | ||
local show_etymology_languages = to_boolean(args[3] or args.etym) | local show_etymology_languages = to_boolean(args[3] or args.etym) | ||
-- Get the value for lemma_count argument | |||
local lemma_count = to_boolean(args.lemma_count) | |||
-- help! parameter name too long! | -- help! parameter name too long! | ||
| Line 283: | Line 288: | ||
end | end | ||
return | return export.print_children(descendants_of, { | ||
hide_families_with_protolanguages = not show_all_families, | hide_families_with_protolanguages = not show_all_families, | ||
hide_etymology_languages = not show_etymology_languages, | hide_etymology_languages = not show_etymology_languages, | ||
| Line 289: | Line 294: | ||
protolanguage_under_family = protolanguage_under_family, | protolanguage_under_family = protolanguage_under_family, | ||
sterile_branch_length = sterile_branch_length, | sterile_branch_length = sterile_branch_length, | ||
collapsed = require("Module:yesno")(args.collapsed) | collapsed = require("Module:yesno")(args.collapsed), | ||
lemma_count = lemma_count | |||
}) | }) | ||
end | end | ||
function | function export.print_children(descendants_of, options) | ||
local | local m_languages = require("Module:languages") | ||
local m_table = require("Module:table") | |||
local make_auto_subtabler = require("Module:auto-subtable") | |||
descendants_of = m_languages.getByCode(descendants_of, nil, true, true) | |||
local names = {} | |||
local protolanguage_of = {} | |||
local children = make_auto_subtabler{} | |||
local descendants = descendants_of:getDescendantCodes() | |||
table.insert(descendants, descendants_of:getCode()) | |||
if descendants_of:hasType("family") then | |||
protolanguage_of[descendants_of:getCode()] = descendants_of:getProtoLanguageCode() | |||
end | |||
local memoized = {} | |||
local get = function(code, func, ...) | |||
local ret = memoized[code] or func(...) | |||
if code then | |||
memoized[code] = ret | |||
end | |||
return ret | |||
end | |||
for _, descendant_code in ipairs(descendants) do | |||
-- Inner "repeat until true" loop allows break to work like continue, as it will always only run once. | |||
repeat | |||
local descendant = get(descendant_code, m_languages.getByCode, descendant_code, nil, true, true) | |||
names[descendant_code] = descendant:getCanonicalName():gsub("Proto%-", "") | |||
if descendant:hasType("language") then | |||
local ancestors = m_table.shallowCopy(descendant:getAncestorCodes()) | |||
local parent_code = descendant:getParentCode() | |||
if parent_code and descendant:hasType("etymology-only") then | |||
local parent = get(parent_code, descendant.getParent, descendant) | |||
if m_table.deepEquals(parent:getAncestorCodes(), ancestors) and | |||
descendant:getFamilyCode() == parent:getFamilyCode() then | |||
table.insert(children[parent:getCode()], descendant_code) | |||
break | |||
end | |||
end | |||
if #ancestors > 0 then | |||
for _, ancestor in ipairs(ancestors) do | |||
table.insert(children[ancestor], descendant_code) | |||
end | |||
break | |||
end | |||
else | |||
local protolang = descendant:getProtoLanguageCode() | |||
protolanguage_of[descendant_code] = protolang | |||
if protolang and descendant:hasAncestor(protolang) then | |||
table.insert(children[protolang], descendant_code) | |||
break | |||
end | |||
end | |||
local family_code = descendant:getFamilyCode() | |||
if family_code then | |||
local family = get(family_code, descendant.getFamily, descendant) | |||
local protolang = get(family:getProtoLanguageCode(), family.getProtoLanguage, family) | |||
if not protolanguage_of[family] then | |||
protolanguage_of[family] = protolang and protolang:getCode() | |||
end | |||
if protolang and protolang:inFamily(family) and protolang:getCode() ~= descendant_code then | |||
table.insert(children[protolang:getCode()], descendant_code) | |||
else | |||
table.insert(children[family:getCode()], descendant_code) | |||
end | |||
end | |||
until true | |||
end | |||
-- No more auto subtabling needed. | |||
children = children:un_auto_subtable() | |||
-- Copy to new table, to filter out unwanted ancestors from descendants with multiple ancestors, where some are not descendants of the target language. | |||
local parent_to_children_map = {} | |||
for _, code in ipairs(descendants) do | |||
parent_to_children_map[code] = children[code] | |||
end | |||
local function make_nested(data, children) | |||
local make_nil = {} | |||
for key, val in pairs(data) do | |||
if type(key) == "number" then | |||
if children[val] then | |||
data[val] = make_nested(children[val], children) | |||
table.insert(make_nil, key) | |||
end | |||
else | |||
data[key] = make_nested(val, children) | |||
end | |||
end | |||
if make_nil[2] then -- Make sure larger keys are removed first. | |||
table.sort(make_nil, function (a, b) return a > b end) | |||
end | |||
for _, key in ipairs(make_nil) do | |||
table.remove(data, key) | |||
end | |||
return data | |||
end | |||
local nested = make_nested(parent_to_children_map, parent_to_children_map) | |||
local function deep_sort(current) | |||
local result = {} | |||
local is_table = {} | |||
for key, val in pairs(current) do | |||
if type(key) == "number" then | |||
table.insert(result, val) | |||
else | |||
is_table[key] = true | |||
table.insert(result, key) | |||
end | |||
end | |||
table.sort(result, function(code1, code2) | |||
return names[code1] < names[code2] | |||
end) | |||
for i = 1, #result do | |||
if is_table[result[i]] then | |||
local name = result[i] | |||
result[i] = deep_sort(current[result[i]]) | |||
result[i].name = name | |||
else | |||
result[i] = { name = result[i] } | |||
end | |||
end | |||
return result | |||
end | |||
nested = deep_sort(nested) | |||
data = { nested = nested, protolanguage_of = protolanguage_of } | |||
local nested_data, protolanguage_of = data.nested, data.protolanguage_of | local nested_data, protolanguage_of = data.nested, data.protolanguage_of | ||
nested_data = export.find_subtree(nested_data, descendants_of:getCode()) | |||
-- Return nil instead of a tree with only the root node. | -- Return nil instead of a tree with only the root node. | ||
if options.must_have_descendants and (#nested_data == 0 or nested_data[1] and #nested_data[1] == 0) then | if options.must_have_descendants and (nested_data == nil or #nested_data == 0 or nested_data[1] and #nested_data[1] == 0) then | ||
return nil | return nil | ||
end | end | ||
| Line 318: | Line 459: | ||
end | end | ||
local result = | local result = {'<div class="familytree"><ul>'} | ||
local function ins(val) | |||
table.insert(result, val) | |||
end | |||
local tree_options = { | local tree_options = { | ||
| Line 326: | Line 470: | ||
family_under_protolanguage = options.family_under_protolanguage, | family_under_protolanguage = options.family_under_protolanguage, | ||
protolanguage_under_family = options.protolanguage_under_family, | protolanguage_under_family = options.protolanguage_under_family, | ||
lemma_count = options.lemma_count, | |||
} | } | ||
| Line 331: | Line 476: | ||
for i, subtable in ipairs(nested_data) do | for i, subtable in ipairs(nested_data) do | ||
-- top language name | -- top language name | ||
ins('<li>') | |||
-- name me! | -- name me! | ||
| Line 348: | Line 493: | ||
end | end | ||
ins(top_node) | |||
-- top toggle | -- top toggle | ||
local customcollapsible_id = get_customcollapsible_id() | local customcollapsible_id = get_customcollapsible_id() | ||
ins('<span class="familytree-toptoggle mw-customtoggle-' | |||
.. customcollapsible_id .. '" style="display: none;">') | .. customcollapsible_id .. '" style="display: none;">') | ||
ins(options.collapsed and expandtext or collapsetext) | |||
ins('</span>') | |||
if flag then | if flag then | ||
ins('<li>') | |||
ins(next_node) | |||
end | end | ||
-- tree | -- tree | ||
ins('<ul class="mw-collapsible') | |||
if options.collapsed then | if options.collapsed then | ||
ins(' mw-collapsed') | |||
end | end | ||
ins('" id="mw-customcollapsible-' .. customcollapsible_id) | |||
ins('" data-expandtext="' .. expandtext) | |||
ins('" data-collapsetext="' .. collapsetext .. '">') | |||
ins(make_tree(subtable, is_protolanguage, protolanguage_of, tree_options)) | |||
if flag then | if flag then | ||
ins('</li>') | |||
end | end | ||
ins('</ul></li>') | |||
end | end | ||
ins('</ul></div>') | |||
ins(require("Module:TemplateStyles")("Module:family tree/style.css")) | |||
return table.concat(result) | |||
end | end | ||
return | return export | ||