Zum Inhalt springen

Modul:AufEinWortSeiteKennung

aus AufEinWort, der freien Forschung

-- Modul:AufEinWortSeiteKennung (Erweiterung UD, bereinigt)
-- Zweck: kompakte, numerische und chronologisch sortierbare Kennung (YIDXMMDDNN)
-- YIDX = 5000 + Jahr (n. Chr.) bzw. 5000 - Jahr (v. Chr.), Jahr ∈ [1..4999]
-- MM/DD = zweistellig oder "00" bei Unbekannt; NN = zweistellige Nummer

local p = {}

-- =========================
-- Konfiguration
-- =========================
-- "prefix":   ED:7020112000 / BD:7020112000 / UD:7020112000
-- "postfix":  7020112000 ED / 7020112000 BD / 7020112000 UD (empfohlen)
-- "both"  :   beide Varianten
local CATEGORY_STYLE = "postfix"
-- Zusätzlich die reine numerische Kategorie setzen (Übergangsphase)
local LEGACY_NUMERIC = false

-- =========================
-- Hilfsfunktionen
-- =========================
local function normalize_whitespace(s)
	if not s then return s end
	s = mw.ustring.gsub(s, "%s+", " ")
	s = mw.ustring.match(s, "^%s*(.-)%s*$") or s
	return s
end

-- Ära (CE/BCE) erkennen und Eingabe bereinigen
-- Rückgabe: era ("1"=n. Chr., "0"=v. Chr.), cleaned string
local function detectEraAndClean(s)
	local era = "1" -- Standard: n. Chr.
	if not s or s == "" then return era, s end

	local sl = mw.ustring.lower(s)
	sl = mw.ustring.gsub(sl, "%.", " ")    -- Punkte entfernen
	sl = normalize_whitespace(sl)

	-- BCE-Marker erkennen (minus vorne, v. Chr., BCE, BC)
	if mw.ustring.match(sl, "^%-")
		or mw.ustring.find(sl, " v chr")
		or mw.ustring.find(sl, " vor christus")
		or mw.ustring.find(sl, " bce")
		or mw.ustring.find(sl, " bc") then
		era = "0"
	end

	-- Marker entfernen (wo auch immer sie stehen)
	sl = mw.ustring.gsub(sl, "^%-", "")
	sl = mw.ustring.gsub(sl, " v chr", "")
	sl = mw.ustring.gsub(sl, " vor christus", "")
	sl = mw.ustring.gsub(sl, " bce", "")
	sl = mw.ustring.gsub(sl, " bc", "")
	sl = normalize_whitespace(sl)
	return era, sl
end

local function replace_umlauts(s)
	local m = { ["Ä"] = "Ae", ["ä"] = "ae", ["Ö"] = "Oe", ["ö"] = "oe", ["Ü"] = "Ue", ["ü"] = "ue", ["ß"] = "ss" }
	for k, v in pairs(m) do s = mw.ustring.gsub(s, k, v) end
	return s
end

local monate = {
	januar = "01", februar = "02",
	maerz  = "03", ["märz"] = "03",
	april  = "04", mai     = "05",
	juni   = "06", juli    = "07",
	august = "08", september = "09",
	oktober = "10", november = "11",
	dezember = "12"
}

local function month_to_num(name)
	if not name or name == "" then return "00" end
	local asNum = tonumber(name)
	if asNum and asNum >= 1 and asNum <= 12 then
		return string.format("%02d", asNum)
	end
	local key = mw.ustring.lower(name)
	if key == "märz" then key = "maerz" end
	return monate[key] or "00"
end

-- Flexibles Parsen: liefert (tag, monat_name, jahr) oder nils
local function parse_date_flexible(s)
	-- dd.mm.yyyy (auch 1- oder 2-stellige d/m)
	local d, m, y = mw.ustring.match(s, "^(%d%d?)%s*%.%s*(%d%d?)%s*%.%s*(%d+)$")
	if d and m and y then return d, m, y end
	-- yyyy-mm-dd
	local y2, m2, d2 = mw.ustring.match(s, "^(%d+)%-(%d%d?)%-(%d%d?)$")
	if y2 and m2 and d2 then return d2, m2, y2 end
	-- Tag[.] Monat Jahr (z. B. "12. Mai 425" oder "12 Mai 425")
	local tag, monat_name, jahr = mw.ustring.match(s, "^(%d+)%.?%s*(%a+)%s*(%d+)$")
	if tag and monat_name and jahr then return tag, monat_name, jahr end
	-- Monat Jahr (z. B. "Mai 425")
	monat_name, jahr = mw.ustring.match(s, "^(%a+)%s*(%d+)$")
	if monat_name and jahr then return nil, monat_name, jahr end
	-- Nur Jahr (z. B. "425")
	jahr = mw.ustring.match(s, "^(%d+)$")
	if jahr then return nil, nil, jahr end
	return nil, nil, nil
end

-- PIVOT-Kodierung des Jahres: 5000 ± Jahr → 4-stellig
local function yearIndex(era, y)
	if y < 1 then y = 1 end -- Jahr 0 vermeiden
	if era == "0" then
		local idx = 5000 - y
		if idx < 0 then idx = 0 end
		return idx
	else
		local idx = 5000 + y
		if idx > 9999 then
			return nil, "Fehler: Jahreszahl außerhalb des Bereichs (1–4999)"
		end
		return idx
	end
end

-- Kern: Kennung erzeugen (YIDXMMDDNN)
local function makeKennung(datum, nummer)
	local num = string.format("%02d", tonumber(nummer) or 0)
	if not datum or datum == "" then
		return "00000000" .. num, nil
	end
	local era, cleaned = detectEraAndClean(datum)
	local normalized = replace_umlauts(cleaned)
	local tag, monat_name, jahr = parse_date_flexible(normalized)
	if not jahr then
		return "Fehler: Ungültiges Datumsformat", { badFormat = true }
	end
	local y = tonumber(jahr)
	if not y then
		return "Fehler: Ungültige Jahreszahl", { badYear = true }
	end
	local yidx, err = yearIndex(era, y)
	if not yidx then
		return (err or "Fehler bei Jahresindex"), { badYear = true }
	end
	local Y = string.format("%04d", yidx)
	local M = month_to_num(monat_name)
	local warn = nil
	if monat_name and M == "00" then
		warn = warn or {}
		warn.badMonth = true
	end
	local D = tag and string.format("%02d", tonumber(tag) or 0) or "00"
	return Y .. M .. D .. num, warn
end

-- =========================
-- Öffentliche API
-- =========================

-- Lesbare Anzeige (z. B. für Listen oder DISPLAYTITLE)
local monatsNamen = {"Januar","Februar","März","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"}
function p.anzeige(frame)
	local a_self   = frame.args or {}
	local a_parent = (frame.getParent and frame:getParent().args) or {}
	-- Priorität: ED → BD → UD → Datum
	local datum = a_self.Erstelldatum or a_self.Bearbeitetdatum or a_self.Urwerkdatum or a_self.Datum
	if not datum or datum == "" then
		datum = a_parent.Erstelldatum or a_parent.Bearbeitetdatum or a_parent.Urwerkdatum or a_parent.Datum or ""
	end
	if not datum or datum == "" then return "unbekannt" end

	local era, cleaned = detectEraAndClean(datum)
	local normalized = replace_umlauts(cleaned)
	local tag, monat_name, jahr = parse_date_flexible(normalized)
	if not jahr then return "unbekannt" end
	local y = tonumber(jahr) or 1
	local eraLabel = (era == "0") and " v. Chr." or " n. Chr."

	local mtxt = ""
	if monat_name then
		local mnum = month_to_num(monat_name)
		local mn = tonumber(mnum)
		if mn and mn >= 1 and mn <= 12 then mtxt = monatsNamen[mn] end
	end
	if tag and mtxt ~= "" then
		return string.format("%d. %s %d%s", tonumber(tag) or 0, mtxt, y, eraLabel)
	elseif mtxt ~= "" then
		return string.format("%s %d%s", mtxt, y, eraLabel)
	else
		return string.format("%d%s", y, eraLabel)
	end
end

-- Kennung (YIDXMMDD[NN]) → lesbarer Titel (für Kategorieseiten)
function p.decodeKennung(frame)
	local code = (frame.args and frame.args.Kennung) or nil
	if not code or code == "" then code = mw.title.getCurrentTitle().text or "" end
	local digits = code:match("^(%d+)$")
	if not digits then digits = mw.ustring.gsub(code, "[^%d]", "") end
	local Y, M, D = digits:match("^(%d%d%d%d)(%d%d)(%d%d)")
	if not (Y and M and D) then return code end
	local yidx = tonumber(Y); if not yidx then return code end
	local era = " n. Chr."; local year
	if yidx < 5000 then era = " v. Chr."; year = 5000 - yidx else year = yidx - 5000 end
	local mnum = tonumber(M) or 0
	local dnum = tonumber(D) or 0
	if mnum == 0 and dnum >= 1 and dnum <= 12 then mnum, dnum = dnum, 0 end -- Alt-/Fremdcodes
	local mn_ok = (mnum >= 1 and mnum <= 12)
	if dnum > 0 and mn_ok then
		return string.format("%d. %s %d%s", dnum, monatsNamen[mnum], year, era)
	elseif mn_ok then
		return string.format("%s %d%s", monatsNamen[mnum], year, era)
	else
		return string.format("%d%s", year, era)
	end
end

-- Decode + Suffix anhand Kategorientitel (ED/BD/UD)
function p.decodeKennungMitSuffix(frame)
	local args  = frame.args or {}
	local title = mw.title.getCurrentTitle().text or ""
	local base = p.decodeKennung({ args = { Kennung = title } })
	if not base or base == "" then return base end
	local isED = mw.ustring.match(title, "%sED$") or mw.ustring.match(title, "^ED:")
	local isBD = mw.ustring.match(title, "%sBD$") or mw.ustring.match(title, "^BD:")
	local isUD = mw.ustring.match(title, "%sUD$") or mw.ustring.match(title, "^UD:")
	local token = ""
	if isED then token = args.ED or "erst."
	elseif isBD then token = args.BD or "bear."
	elseif isUD then token = args.UD or "hrsg." end
	if token == "" then return base end
	local sep = args.sep or " "
	local pos = args.pos or "after"
	if pos == "before" then return token .. sep .. base else return base .. sep .. token end
end

-- Interne Helfer: Kategorieausgabe mit Prä-/Postfix
local function makeCatOutput(datum, nummer, token, opts)
	local code, warn = makeKennung(datum, nummer)
	local out = ""
	if mw.ustring.match(code, "^%d%d%d%d%d%d%d%d%d%d") then
		local style = (opts and opts.style) or CATEGORY_STYLE
		if style == "prefix" or style == "both" then
			out = out .. '<span style="display:none">[[Kategorie:' .. (token .. ":" .. code) .. ']]</span>'
		end
		if style == "postfix" or style == "both" then
			out = out .. '<span style="display:none">[[Kategorie:' .. (code .. " " .. token) .. ']]</span>'
		end
		if LEGACY_NUMERIC then
			out = out .. '<span style="display:none">[[Kategorie:' .. code .. ']]</span>'
		end
	end
	if warn and warn.badMonth then
		out = out .. '<span style="display:none">[[Kategorie:AufEinWort-Datum-fehlerhaft]]</span>'
	end
	return out
end

-- Jahrhundert-Kategorie aus Datum (auf Werkseiten)
local function makeCenturyCategoryFromDate(datum, opts)
	if not datum or datum == "" then return "" end
	local era, cleaned = detectEraAndClean(datum)
	local normalized = replace_umlauts(cleaned)
	local _t, _m, jahr = parse_date_flexible(normalized)
	if not jahr then return "" end
	local y = tonumber(jahr); if not y then return "" end
	if y < 1 then y = 1 end
	local eraCE = (era ~= "0")
	local century = math.floor((y - 1) / 100) + 1
	local form = (opts and opts.form) or "kurz"
	local term = (form == "lang") and "Jahrhundert" or "Jh."
	local nchr = (opts and opts.nchr) or "n. Chr."
	local vchr = (opts and opts.vchr) or "v. Chr."
	local label = tostring(century) .. ". " .. term .. " " .. (eraCE and nchr or vchr)
	return "[[Kategorie:" .. label .. "]]"
end

-- Kategorien auf Werkseiten
function p.catErstellung(frame)
	local args   = frame.args or frame:getParent().args or {}
	local datum  = args.Erstelldatum or ""
	local nummer = args.Nummer or "00"
	local style  = args.style
	local out = makeCatOutput(datum, nummer, "ED", { style = style })
	out = out .. makeCenturyCategoryFromDate(datum, { form = args.form, nchr = args.nchr, vchr = args.vchr })
	return out
end

function p.catBearbeitung(frame)
	local args   = frame.args or frame:getParent().args or {}
	local datum  = args.Bearbeitetdatum or ""
	local nummer = args.Nummer or "00"
	local style  = args.style
	local out = makeCatOutput(datum, nummer, "BD", { style = style })
	out = out .. makeCenturyCategoryFromDate(datum, { form = args.form, nchr = args.nchr, vchr = args.vchr })
	return out
end

function p.catUrwerk(frame)
	local args   = frame.args or frame:getParent().args or {}
	local datum  = args.Urwerkdatum or ""
	local nummer = args.Nummer or "00"
	local style  = args.style
	local out = makeCatOutput(datum, nummer, "UD", { style = style })
	out = out .. makeCenturyCategoryFromDate(datum, { form = args.form, nchr = args.nchr, vchr = args.vchr })
	return out
end

-- Kennungen einzeln
function p.kennungE(frame)
	local args   = frame.args or (frame.getParent and frame:getParent().args) or {}
	local datum  = args.Erstelldatum or ""
	local nummer = args.Nummer or "00"
	return makeKennung(datum, nummer)
end

function p.kennungB(frame)
	local args   = frame.args or (frame.getParent and frame:getParent().args) or {}
	local datum  = args.Bearbeitetdatum or ""
	local nummer = args.Nummer or "00"
	return makeKennung(datum, nummer)
end

function p.kennungU(frame)
	local args   = frame.args or (frame.getParent and frame:getParent().args) or {}
	local datum  = args.Urwerkdatum or ""
	local nummer = args.Nummer or "00"
	return makeKennung(datum, nummer)
end

-- Meta-Kategorien für Kategorieseiten (ED/BD/UD)
function p.katMeta(frame)
	local args   = frame.args or {}
	local title  = mw.title.getCurrentTitle().text or ""
	local isED = mw.ustring.match(title, "%sED$") or mw.ustring.match(title, "^ED:")
	local isBD = mw.ustring.match(title, "%sBD$") or mw.ustring.match(title, "^BD:")
	local isUD = mw.ustring.match(title, "%sUD$") or mw.ustring.match(title, "^UD:")
	local digits = mw.ustring.gsub(title, "[^%d]", "") or ""
	local Y, M, D = digits:match("^(%d%d%d%d)(%d%d)(%d%d)")
	local gran = "year"
	if Y and M and D then
		if D ~= "00" then gran = "day"
		elseif M ~= "00" then gran = "month"
		else gran = "year" end
	end
	local edCat     = args.ED       or "Kategorie:Werk Erstelldatum"
	local bdCat     = args.BD       or "Kategorie:Werk Bearbeitetdatum"
	local udCat     = args.UD       or "Kategorie:Werk Urwerkdatum"
	local edTagCat  = args.ED_Tag   or args.EDTag    or "Kategorie:Werk Erstellungskennung Tag"
	local edMonCat  = args.ED_Monat or args.EDMonat  or "Kategorie:Werk Erstellungskennung Monat"
	local edJahrCat = args.ED_Jahr  or args.EDJahr   or "Kategorie:Werk Erstellungskennung Jahr"
	local bdTagCat  = args.BD_Tag   or args.BDTag    or "Kategorie:Werk Bearbeitungskennung Tag"
	local bdMonCat  = args.BD_Monat or args.BDMonat  or "Kategorie:Werk Bearbeitungskennung Monat"
	local bdJahrCat = args.BD_Jahr  or args.BDJahr   or "Kategorie:Werk Bearbeitungskennung Jahr"
	local udTagCat  = args.UD_Tag   or args.UDTag    or "Kategorie:Werk Urwerkkennung Tag"
	local udMonCat  = args.UD_Monat or args.UDMonat  or "Kategorie:Werk Urwerkkennung Monat"
	local udJahrCat = args.UD_Jahr  or args.UDJahr   or "Kategorie:Werk Urwerkkennung Jahr"

	if isED then
		if gran == "day" then return "[[" .. edTagCat .. "]]" end
		if gran == "month" then return "[[" .. edMonCat .. "]]" end
		if gran == "year" then return "[[" .. edJahrCat .. "]]" end
		return "[[" .. edCat .. "]]"
	elseif isBD then
		if gran == "day" then return "[[" .. bdTagCat .. "]]" end
		if gran == "month" then return "[[" .. bdMonCat .. "]]" end
		if gran == "year" then return "[[" .. bdJahrCat .. "]]" end
		return "[[" .. bdCat .. "]]"
	elseif isUD then
		if gran == "day" then return "[[" .. udTagCat .. "]]" end
		if gran == "month" then return "[[" .. udMonCat .. "]]" end
		if gran == "year" then return "[[" .. udJahrCat .. "]]" end
		return "[[" .. udCat .. "]]"
	end
	return ""
end

-- Jahrhundert-Metakategorie je nach Datum (für Kategorieseiten)
-- Fügt automatisch eine Jahrhundert-Kategorie hinzu, z. B. "21. Jh. n. Chr." / "1. Jh. v. Chr."
-- Erkennung basiert auf dem Kategorietitel (Post-/Prefix mit ED/BD/UD), Jahreszahl aus YIDXMMDD
-- Optional: split=1 → eigene Jahrhundert-Kategorien für ED/BD/UD (Suffix " ED"/" BD"/" UD"), sonst gemeinsam
function p.katJahrhundert(frame)
	local args   = frame.args or {}
	local title  = mw.title.getCurrentTitle().text or ""
	local isED = mw.ustring.match(title, "%sED$") or mw.ustring.match(title, "^ED:")
	local isBD = mw.ustring.match(title, "%sBD$") or mw.ustring.match(title, "^BD:")
	local isUD = mw.ustring.match(title, "%sUD$") or mw.ustring.match(title, "^UD:")
	local digits = mw.ustring.gsub(title, "[^%d]", "") or ""
	local Y, M, D = digits:match("^(%d%d%d%d)(%d%d)(%d%d)")
	if not (Y and M and D) then return "" end
	local yidx = tonumber(Y); if not yidx then return "" end
	local eraCE = true; local year
	if yidx < 5000 then eraCE = false; year = 5000 - yidx else year = yidx - 5000 end
	if year < 1 then year = 1 end
	local century = math.floor((year - 1) / 100) + 1
	local form = args.form or "kurz"
	local term = (form == "lang") and "Jahrhundert" or "Jh."
	local nchr = args.nchr or "n. Chr."
	local vchr = args.vchr or "v. Chr."
	local label = tostring(century) .. ". " .. term .. " " .. (eraCE and nchr or vchr)
	local split = tostring(args.split or "0")
	if split == "1" then
		if isED then label = label .. " ED"
		elseif isBD then label = label .. " BD"
		elseif isUD then label = label .. " UD" end
	end
	return "[[Kategorie:" .. label .. "]]"
end

return p