모듈:category tree
보이기
틀:자동 분류의 기준이 되는 묘듈
함부로 만지면 분류체계가 무너지거나 틀:자동 분류포함 문서들이 단체로 오류를 내뱉음으로 조심해야 됨
-- 치환 방지
if mw.isSubsting() then
return require("Module:unsubst")
end
local export = {}
local category_tree_submodule_prefix = "Module:category tree/"
local category_tree_styles_css = "Module:category tree/styles.css"
local m_str_utils = require("Module:string utilities")
local m_template_parser = require("Module:template parser/sandbox")
local m_utilities = require("Module:utilities")
local ceil = math.ceil
local class_else_type = m_template_parser.class_else_type
local concat = table.concat
local deep_copy = require("Module:table").deepCopy
local full_url = mw.uri.fullUrl
local insert = table.insert
local is_callable = require("Module:fun").is_callable
local log10 = math.log10 or require("Module:math").log10
local new_title = mw.title.new
local pages_in_category = mw.site.stats.pagesInCategory
local parse = m_template_parser.parse
local remove_comments = require("Module:string/removeComments")
local sort = table.sort
local split = m_str_utils.split
local string_compare = require("Module:string/compare")
local trim = m_str_utils.trim
local uupper = m_str_utils.upper
local yesno = require("Module:yesno")
local current_frame = mw.getCurrentFrame()
local current_title = mw.title.getCurrentTitle()
local namespace = current_title.namespace
local poscatboiler_subsystem = "poscatboiler"
local extra_args_error = "이 분류에서는 {{((}}auto cat{{))}}에 추가 인수를 사용할 수 없습니다."
-- 숫자 `n`에 대한 정렬 키를 생성합니다. "1, 10, 2, 3" 순서로 정렬되는 문제를 피하기 위해 앞에 0을 붙입니다.. `max_n`은 `n`의 예상 최댓값으로, 몇 개의 0을 붙일지 결정하는 데 사용됩니다. 지정되지 않으면 전체 언어 수로 기본값이 설정됩니다.
function export.numeral_sortkey(n, max_n)
max_n = max_n or require("Module:list of languages").count()
return ("#%%0%dd"):format(ceil(log10(max_n + 1))):format(n)
end
function export.split_lang_label(title_text)
local getByCanonicalName = require("Module:languages").getByCanonicalName
-- 잠재적인 정식 이름에서 단어를 점진적으로 제거해가며
-- 실제 정식 이름과 일치하는 것을 찾습니다.
local words = split(title_text, " ", true)
for i = #words - 1, 1, -1 do
local lang = getByCanonicalName(concat(words, " ", 1, i))
if lang then
return lang, concat(words, " ", i + 1)
end
end
return nil, title_text
end
local function show_error(text)
return require("Module:message box/sandbox").maintenance(
"red",
"[[파일:Ambox warning pn.svg|50px]]",
"이 분류는 위키낱말사전의 분류 체계에 정의되어 있지 않습니다.",
text
)
end
-- 페이지 우측 상단에 표시될 내용을 보여줍니다.
local function show_topright(current)
return current.getTopright and current:getTopright() or nil
end
local function link_box(content)
return ("<div class=\"noprint plainlinks\" style=\"float: right; clear: both; margin: 0 0 .5em 1em; background: var(--wikt-palette-paleblue, #f9f9f9); border: 1px var(--border-color-base, #aaaaaa) solid; margin-top: -1px; padding: 5px; font-weight: bold;\">%s</div>"):format(content)
end
local function show_editlink(current)
return link_box(("[%s 분류 데이터 편집]"):format(tostring(full_url(current:getDataModule(), "action=edit"))))
end
function show_related_changes()
local title = current_title.fullText
return link_box(("[%s <span title=\"%s에 속한 문서들의 최근 편집과 기타 바뀜\">최근 바뀜</span>]"):format(
tostring(full_url("Special:RecentChangesLinked", {
target = title,
showlinkedto = 0,
})),
title
))
end
local function show_pagelist(current)
local namespace = "namespace="
local info = current:getInfo()
local lang_code = info.code
if info.label == "citations" or info.label == "citations of undefined terms" then
namespace = namespace .. "Citations"
elseif lang_code then
local lang = require("Module:languages").getByCode(lang_code, true)
if lang then
-- 'gmq-pro'(원시 노르드어)는 재구된 항목이 아닌 일반 항목을 주로 가질 것으로 예상되는
-- '-pro'로 끝나는 코드의 언어입니다.
if (lang_code:find("%-pro$") and lang_code ~= "gmq-pro") or lang:hasType("reconstructed") then
namespace = namespace .. "Reconstruction"
elseif lang:hasType("appendix-constructed") then
namespace = namespace .. "Appendix"
end
end
elseif info.label:match("templates") then
namespace = namespace .. "Template"
elseif info.label:match("modules") then
namespace = namespace .. "Module"
elseif info.label:match("^Wiktionary") or info.label:match("^Pages") then
namespace = ""
end
return ([=[
{| id="newest-and-oldest-pages" class="wikitable mw-collapsible" style="float: right; clear: both; margin: 0 0 .5em 1em;"
! 가장 새로운/오래된 문서
|-
| id="recent-additions" style="font-size:0.9em;" | '''가장 최근에 추가된 문서:'''
%s
|-
| id="oldest-pages" style="font-size:0.9em;" | '''가장 오래된 문서:'''
%s
|}]=]):format(
current_frame:extensionTag(
"DynamicPageList",
([=[
category=%s
%s
count=10
mode=ordered
ordermethod=categoryadd
order=descending]=]
):format(current_title.text, namespace)
),
current_frame:extensionTag(
"DynamicPageList",
([=[
category=%s
%s
count=10
mode=ordered
ordermethod=lastedit
order=ascending]=]
):format(current_title.text, namespace)
)
)
end
-- 페이지 상단의 이동 경로("breadcrumbs")를 보여줍니다.
local function show_breadcrumbs(current)
local steps = {}
-- 현재 레이블에서 시작하여 부모로 거슬러 올라가며 "사슬"을 만듭니다.
while current do
local category, display_name, nocap
if type(current) == "string" then
category = current
display_name = current:gsub("^분류:", "")
else
if not current.getCategoryName then
error("내부 오류: 이동 경로 구조의 형식이 잘못되었습니다. `parents` 값의 형식이 잘못되었을 수 있습니다: " ..
mw.dumpObject(current))
end
category = "분류:" .. current:getCategoryName()
display_name, nocap = current:getBreadcrumbName()
end
if not nocap then
display_name = mw.getContentLanguage():ucfirst(display_name)
end
insert(steps, 1, ("[[:%s|%s]]"):format(category, display_name))
-- "사슬"을 한 단계 위로 이동합니다.
if type(current) == "string" then
current = nil
else
current = current:getParents()
end
if current then
current = current[1].name
end
end
local templateStyles = require("Module:TemplateStyles")(category_tree_styles_css)
local ol = mw.html.create("ol")
for i, step in ipairs(steps) do
local li = mw.html.create("li")
if i ~= 1 then
local span = mw.html.create("span")
:attr("aria-hidden", "true")
:addClass("ts-categoryBreadcrumbs-separator")
:wikitext(" » ")
li:node(span)
end
li:wikitext(step)
ol:node(li)
end
return templateStyles .. tostring(mw.html.create("div")
:attr("role", "navigation")
:attr("aria-label", "이동 경로")
:addClass("ts-categoryBreadcrumbs")
:node(ol))
end
local function show_also(current)
local also = current._info.also
if also and #also > 0 then
local new_frame = mw.getCurrentFrame():newChild{ args = also }
function new_frame:getParent()
return self
end
local also_text = require("Module:also/templates").also_t(new_frame)
return ('<div style="margin-top:-1em;margin-bottom:1.5em">%s</div>'):format(also_text)
end
return nil
end
-- 분류에 대한 간단한 설명 텍스트를 보여줍니다.
local function show_description(current)
return current.getDescription and current:getDescription() or nil
end
local function show_appendix(current)
local appendix = current.getAppendix and current:getAppendix()
return appendix and ("자세한 내용은 [[%s]] 문서를 참고하십시오."):format(appendix) or nil
end
local function sort_children(child1, child2)
return string_compare(uupper(child1.sort), uupper(child2.sort))
end
-- 하위 분류 목록을 보여줍니다.
local function show_children(current)
local children = current.getChildren and current:getChildren() or nil
if not children then
return nil
end
sort(children, sort_children)
local children_list = {}
for _, child in ipairs(children) do
local child_name, child_pagetitle = child.name
if type(child_name) == "string" then
child_pagetitle = child_name
else
child_pagetitle = "분류:" .. child_name:getCategoryName()
end
if new_title(child_pagetitle).exists then
insert(children_list, ("* [[:%s]]: %s"):format(
child_pagetitle,
child.description or
type(child_name) == "string" and child_name:gsub("^분류:", "") .. "." or
child_name:getDescription("child")
))
end
end
return concat(children_list, "\n")
end
-- 언어의 문자 체계에 맞춰 목차를 보여줍니다.
local function show_TOC(current)
local titleText = current_title.text
local inCategoryPages = pages_in_category(titleText, "pages")
local inCategorySubcats = pages_in_category(titleText, "subcats")
local TOC_type
-- 필요한 목차 유형을 계산합니다.
if inCategoryPages > 2500 or inCategorySubcats > 2500 then
TOC_type = "full"
elseif inCategoryPages > 200 or inCategorySubcats > 200 then
TOC_type = "normal"
else
-- 모든 문서나 하위 분류가 한 페이지에 들어갈 수 있다면 보통 목차가 필요 없음.
-- 하지만 사용자 정의 TOC 핸들러로 이를 재정의할 수 있음.
TOC_type = "none"
end
if current.getTOC then
local TOC_text = current:getTOC(TOC_type)
if TOC_text ~= true then
return TOC_text or nil
end
end
if TOC_type ~= "none" then
local templatename = current:getTOCTemplateName()
local TOC_template
if TOC_type == "full" then
-- 이 분류가 매우 크므로, "/full" 버전의 목차가 있는지 확인합니다.
local TOC_template_full = new_title(templatename .. "/full")
if TOC_template_full.exists then
TOC_template = TOC_template_full
end
end
if not TOC_template then
local TOC_template_normal = new_title(templatename)
if TOC_template_normal.exists then
TOC_template = TOC_template_normal
end
end
if TOC_template then
return current_frame:expandTemplate{title = TOC_template.text, args = {}}
end
end
return nil
end
-- 페이지에 언어 속성과 스크립트 클래스를 추가하는 "catfix"를 보여줍니다.
local function show_catfix(current)
local lang, sc = current:getCatfixInfo()
return lang and m_utilities.catfix(lang, sc) or nil
end
-- 현재 분류가 속해야 하는 상위 분류를 보여줍니다.
local function show_categories(current, categories)
local parents = current.getParents and current:getParents() or nil
if not parents then
return nil
end
for _, parent in ipairs(parents) do
local parent_name = parent.name
local sortkey = type(parent.sort) == "table" and parent.sort:makeSortKey() or parent.sort
if type(parent_name) == "string" then
insert(categories, ("[[%s|%s]]"):format(parent_name, sortkey))
else
insert(categories, ("[[분류:%s|%s]]"):format(parent_name:getCategoryName(), sortkey))
end
end
-- 해당 분류를 "포괄" 또는 "언어별" 분류에도 배치합니다.
local umbrella = current:getUmbrella()
if umbrella then
local sortkey = current._lang and current._lang:getCanonicalName() or current:getCategoryName()
sortkey = require("Module:languages").getByCode("ko", true):makeSortKey(sortkey)
if type(umbrella) == "string" then
insert(categories, ("[[%s|%s]]"):format(umbrella, sortkey))
else
insert(categories, ("[[분류:%s|%s]]"):format(umbrella:getCategoryName(), sortkey))
end
end
-- 분류 트리 데이터에 통합되어야 할 원치 않는 파서 함수들을 확인합니다.
local content = current_title:getContent()
if not content then
-- 존재하지 않는 분류 페이지에서 {{auto cat}}을 호출할 때 발생할 수 있음.
return
end
local defaultsort, displaytitle, page_has_param
for node in parse(content):iterate_nodes() do
local node_class = class_else_type(node)
if node_class == "template" then
local name = node:get_name()
if name == "DEFAULTSORT:" and not defaultsort then
insert(categories, "[[분류:DEFAULTSORT 충돌이 있는 문서]]")
defaultsort = true
elseif name == "DISPLAYTITLE:" and not displaytitle then
insert(categories,"[[분류:DISPLAYTITLE 충돌이 있는 문서]]")
displaytitle = true
end
elseif node_class == "parameter" and not page_has_param then
insert(categories,"[[분류:처리되지 않은 삼중 괄호 매개변수가 있는 문서]]")
page_has_param = true
end
end
-- 분류 트리 데이터에 통합되어야 할 원시 분류 마크업을 확인합니다.
content = remove_comments(content, "BOTH")
local head = content:find("[[", 1, true)
while head do
local close = content:find("]]", head + 2, true)
if not close then
break
end
local open = content:find("[[", head + 2, true)
while open and open < close do
head = open
open = content:find("[[", head + 2, true)
end
local cat = content:sub(head + 2, close - 1)
local colon = cat:match("^[ _\128-\244]*[Cc분류AaTtEeGgOoRrYy _\128-\244]*():")
if colon then
local pipe = cat:find("|", colon + 1, true)
if pipe ~= #cat then
local title = new_title(pipe and cat:sub(1, pipe - 1) or cat)
if title and title.namespace == 14 then
insert(categories,"[[분류:처리되지 않은 분류 마크업을 사용한 분류]]")
break
end
end
end
head = open
end
end
local function generate_output(current)
if current then
for _, functionName in pairs{
"getBreadcrumbName",
"getDataModule",
"canBeEmpty",
"getDescription",
"getParents",
"getChildren",
"getUmbrella",
"getAppendix",
"getTOCTemplateName",
} do
if not is_callable(current[functionName]) then
require("Module:debug").track{"category tree/missing function", "category tree/missing function/" .. functionName}
end
end
end
local boxes, display, categories = {}, {}, {}
-- 분류는 파일을 갤러리 형태로 표시해서는 안 됩니다.
insert(categories, "__NOGALLERY__")
if current_frame:getParent():getTitle() == "틀:auto cat" then
insert(categories, "[[분류:auto cat 틀을 사용하는 분류]]")
end
local totalPages = pages_in_category(current_title.text, "all")
local hugeCategory = totalPages > 1000000 -- 백만
-- Categorize huge categories, as they cause DynamicPageList to time out and make the category inaccessible.
if hugeCategory then
insert(categories, "[[분류:매우 큰 분류]]")
end
if not current then
insert(categories, "[[분류:분류 체계에 정의되지 않은 분류]]")
insert(categories, totalPages == 0 and "[[분류:비어있는 분류]]" or nil)
insert(display, show_error(
"분류 이름에 오타가 없는지 다시 확인해주세요. <br>" ..
"이 분류가 다른 이름(예: '과일' 대신 '과일류')으로 생성되어야 하는지 확인하려면 [[특수:검색/분류:" .. current_title.text:gsub("^.+:", ""):gsub(" ", "~2 ") .. "~2|기존 분류 검색]]을 이용하세요. <br>" ..
"위키낱말사전의 분류 체계에 새 분류를 추가하려면, " .. current_frame:expandTemplate{title = "틀:section link", args = {
"도움말:분류#분류 만드는 법", -- 한국어 도움말 문서로 링크 수정
}} .. " 문서를 참고해주세요."))
return concat(categories, "") .. concat(display, "\n\n"), true
end
local currentName = current:getCategoryName()
local correctName = current_title.text == currentName
if not correctName then
insert(categories, "[[분류:이름이 올바르지 않은 분류]]")
insert(display, show_error(("분류 체계의 데이터에 따르면, 이 분류의 올바른 이름은 '''[[:분류:%s]]'''입니다."):format(currentName)))
end
local canBeEmpty = current:canBeEmpty()
if canBeEmpty and correctName then
insert(categories, " __EXPECTUNUSEDCATEGORY__")
elseif totalPages == 0 then
insert(categories, "[[분류:비어있는 분류]]")
end
if current:isHidden() then
insert(categories, "__HIDDENCAT__")
end
insert(boxes, "<div style=\"float: right;\">")
insert(boxes, show_topright(current))
insert(boxes, show_editlink(current))
insert(boxes, show_related_changes())
if not hugeCategory then
insert(boxes, show_pagelist(current))
end
insert(boxes, "</div>")
insert(display, show_breadcrumbs(current))
insert(display, show_also(current))
insert(display, show_description(current))
insert(display, show_appendix(current))
insert(display, show_children(current))
insert(display, show_TOC(current))
insert(display, show_catfix(current))
insert(display, '<br class="clear-both-in-vector-2022-only">')
show_categories(current, categories)
return concat(boxes, "\n") .. "\n" .. concat(display, "\n\n") .. concat(categories, "")
end
--[==[
페이지 이름을 분석하여 처리할 하위 모듈을 결정하는 핸들러 함수 목록.
핸들러는 `모듈:category tree`의 하위 모듈 이름과 정보 테이블을 반환해야 함.
페이지 이름을 인식하지 못하면 nil을 반환. 순서가 중요함!
]==]
local handlers = {}
-- Thesaurus 언어별 분류
insert(handlers, function(title)
local code, label = title:match("^Thesaurus:(%l[%a-]*%a):(.+)")
if code then
return poscatboiler_subsystem, {label = title, raw = true}
end
end)
-- 주제별 언어 분류
insert(handlers, function(title)
local code, label = title:match("^(%l[%a-]*%a):(.+)")
if code then
return poscatboiler_subsystem, {label = title, raw = true}
end
end)
-- 방언 등 하위 언어 분류 (예: [[:분류:뉴질랜드 영어]])
insert(handlers, function(title, args)
local lect = args.lect or args.dialect
if lect ~= "" and yesno(lect, true) then
return poscatboiler_subsystem, {label = title, args = args, raw = true}
end
end)
-- poscatboiler 언어별 레이블 (예: [[분류:한국어 명사]])
insert(handlers, function(title, args)
local lang, label = export.split_lang_label(title)
if not lang then
return
end
local baseLabel, script = label:match("(.+) (.-) 문자로 쓰인$")
if script and baseLabel ~= "낱말" then
local scriptObj = require("Module:scripts").getByCanonicalName(script)
if scriptObj then
return poscatboiler_subsystem, {label = baseLabel, code = lang:getCode(), sc = scriptObj:getCode(), args = args}
end
end
return poscatboiler_subsystem, {label = label, code = lang:getCode(), args = args}
end)
-- poscatboiler 레이블 포괄 분류
insert(handlers, function(title, args)
local label = title:match("^언어별 (.+)")
if label then
return poscatboiler_subsystem, {label = label, args = args}
end
end)
-- poscatboiler 원시 핸들러
insert(handlers, function(title, args)
return poscatboiler_subsystem, {label = title, args = args, raw = true}
end)
-- poscatboiler 포괄 핸들러 ('언어별' 없음)
insert(handlers, function(title, args)
return poscatboiler_subsystem, {label = title, args = args}
end)
function export.show(frame)
local args, other_args = require("Module:parameters").process(frame:getParent().args, {
["also"] = {type = "title", sublist = "comma without whitespace", namespace = 14}
}, true)
if args.also then
for k, arg in next, args.also do
args.also[k] = arg.prefixedText
end
end
for k, arg in next, other_args do
other_args[k] = trim(arg)
end
if namespace == 10 then -- 틀(Template) 이름공간
return "(이 틀은 [[도움말:이름공간#분류|분류:]] 이름공간의 문서에서 사용해야 합니다.)"
elseif namespace ~= 14 then -- 분류(Category) 이름공간
error("이 틀/모듈은 [[mw:Help:Namespaces#Category|분류:]] 이름공간의 문서에서만 사용할 수 있습니다.")
end
local first_fail_args_handled, first_fail_cattext
for _, handler in ipairs(handlers) do
local submodule, info = handler(current_title.text, deep_copy(other_args))
if submodule then
info.also = deep_copy(args.also)
require("Module:debug").track("auto cat/" .. submodule)
submodule = require(category_tree_submodule_prefix .. submodule)
local cattext, failed = generate_output(submodule.main(info))
if failed then
if not first_fail_cattext then
first_fail_cattext = cattext
first_fail_args_handled = info.args and true or false
end
elseif not info.args and next(other_args) then
error(extra_args_error)
else
return cattext
end
end
end
if not first_fail_args_handled and next(other_args) then
error(extra_args_error)
end
return first_fail_cattext
end
return export