Zum Inhalt springen

„Modul:Schaufenster“ – Versionsunterschied

aus AufEinWort, der freien Forschung
KKeine Bearbeitungszusammenfassung
Markierung: Zurückgesetzt
KKeine Bearbeitungszusammenfassung
 
(2 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 206: Zeile 206:
     end
     end
     -- Achtung: einfacher Matcher – erwartet "normale" Benutzung (keine verschachtelten "{{" in Parametern)
     -- Achtung: einfacher Matcher – erwartet "normale" Benutzung (keine verschachtelten "{{" in Parametern)
    local patt = "{{%s*[Ss]chaufenster%s*(.-)}}"
-- Match NUR echte {{Schaufenster|...}}-Aufrufe (Pipe nach dem Namen zwingend),
    return (mw.ustring.gsub(text, patt, function(paramstr)
-- vermeidet false positives bei {{SchaufensterBeginn}} / {{SchaufensterEnde}}
        -- Wenn doch verschachtelte "{{" im Param, gib unverändert zurück (Parser macht das dann)
local patt = "{{%s*[Ss]chaufenster%s*(|[^{}]-)}}"
        if paramstr:find("{{", 1, true) then
return (mw.ustring.gsub(text, patt, function(paramstr)
            return "{{Schaufenster" .. paramstr .. "}}"
    -- paramstr beginnt hier mit "|..."; führenden Pipe entfernen
        end
    paramstr = paramstr:gsub("^%s*|", "")
        local a = parseTemplateArgs(paramstr)
    -- Falls verschachtelte Vorlagen im Parameter vorkommen, den Call unverändert lassen
        -- künstlicher Frame für p.zeige (direkter Funktionsaufruf, KEIN Template-Call)
    if paramstr:find("{{", 1, true) then
        local fake = {
        return "{{Schaufenster|" .. paramstr .. "}}"
            args = a,
    end
            -- p.zeige nutzt frame:preprocess → leite auf den äußeren Frame durch
    local a = parseTemplateArgs(paramstr)
            preprocess = function(_, w) return outerFrame:preprocess(w) end,
    local fake = {
            getParent = function() return nil end
        args = a,
        }
        preprocess = function(_, w) return outerFrame:preprocess(w) end,
        getParent = function() return nil end
    }
         -- expandiere eine Ebene tiefer
         -- expandiere eine Ebene tiefer
         return p.zeige(fake, (depth or 1) - 1)
         return p.zeige(fake, (depth or 1) - 1)

Aktuelle Version vom 8. Dezember 2025, 12:48 Uhr

Die Dokumentation für dieses Modul kann unter Modul:Schaufenster/Doku erstellt werden

-- Modul:Schaufenster (VE‑Marker & Suchsteuerung)
-- Zweck:
--   * Transklusion von Quellseiten-Text (komplett, Einleitung, Abschnitt, markierte Spanne)
--   * Optionales Strippen von Kategorien und bestimmten Vorlagen
--   * Steuerung des Suchverhaltens für Zielseiten (CirrusSearch):
--       - "navigation-not-searchable"  → Transklusionsinhalt wird nicht indiziert
--       - "searchaux"                  → Transklusionsinhalt wird nur schwach gewichtet
--   * VE‑taugliche Marker: {{SchaufensterBegin|Label}} … {{SchaufensterEnde|Label}}
--     (kompatibel: {{SchaufensterBeginn|…}}, {{SchaufensterBegin(n)|…}}, {{SchaufensterEnde|…}})
--   * Zusätzlich Erkennung von LST‑Markern: <section begin="Label"/> … <section end="Label"/>
--
-- Aufruf (empfohlen über eine Vorlage, z. B. {{ZitatTransklusion}}):
--   {{#invoke:Schaufenster|zeige
--      | Seite = Werk:Beispiel
--      | Abschnitt = Ein Abschnittstitel             (optional)
--      | Marke = Zitat-001                           (optional; dominiert gegenüber Abschnitt/Lead)
--      | suche = ignorieren | abwerten | (leer)      (optional; Default: ignorieren)
--      | ohneKat = 1/0                               (Default 1)
--      | ohneVorlagen = 1/0                          (Default 1)
--      | link = 1 / eigener Linktext                 (optional: Quelle-Link unterhalb)
--      | refs = 1                                     (optional: <references/> am Ende)
--   }}

local p = {}
local debugModus = false

-- Utils ----------------------------------------------------------------------
local u = {}

function u.trim(s)
	if not s then return "" end
	return (mw.text.trim(tostring(s)))
end

function u.esc(s)
	-- Escape für Lua-Pattern
	return (mw.ustring.gsub(s, "([%(%)%.%%%+%-%*%?%[%^%$])", "%%%1"))
end

-- Einfache Listenlese-Funktion: jede nicht-leere Zeile = Eintrag
local function ladeListeVonSeite(titel)
    local t = mw.title.new(titel)
    if not t or not t.exists then return {} end

    local text = t:getContent() or ""
    -- Zeilenenden vereinheitlichen: \r\n und \r -> \n
    text = mw.ustring.gsub(text, "\r\n?", "\n")
    -- (optional) UTF-8-BOM entfernen
    text = mw.ustring.gsub(text, "^\239\187\191", "")

    local out = {}
    for line in mw.text.gsplit(text, "\n", true) do
        line = mw.text.trim(line)
        if line ~= "" and not line:match("^%-%-") and not line:match("^//") and not line:match("^#") then
            table.insert(out, line)
        end
    end
    return out
end


-- Kategorien entfernen ([[Kategorie:…]])
local function stripKategorien(text)
	-- grob, aber effektiv; mehrfach ausführen ist ok
	text = mw.ustring.gsub(text, "%[%[%s*[Kk]ategorie:%s*[^%]]+%]%]%s*", "")
	return text
end

-- Ausgewählte Vorlagen entfernen (Namen werden aus einer Liste geladen)
local function stripVorlagen(text, namen)
	for _, name in ipairs(namen or {}) do
		local patt = u.esc(name)
		-- Ohne Namensraumpräfix
		text = mw.ustring.gsub(text, "{{%s*" .. patt .. "%s*}}", "")
		text = mw.ustring.gsub(text, "{{%s*" .. patt .. "[^}]*}}", "")
		-- Mit Namensraumpräfix (Vorlage:/Template:)
		text = mw.ustring.gsub(text, "{{%s*[Vv]orlage:%s*" .. patt .. "%s*}}", "")
		text = mw.ustring.gsub(text, "{{%s*[Vv]orlage:%s*" .. patt .. "[^}]*}}", "")
		text = mw.ustring.gsub(text, "{{%s*[Tt]emplate:%s*" .. patt .. "%s*}}", "")
		text = mw.ustring.gsub(text, "{{%s*[Tt]emplate:%s*" .. patt .. "[^}]*}}", "")
	end
	return text
end

-- Abschnitt per Überschrift extrahieren (einfacher Parser)
local function extrahiereBereich(text, heading)
    if not heading or u.trim(heading) == "" then return nil end
    local H = u.esc(heading)
    -- Finde erste Überschrift mit diesem Titel
    local startPos = mw.ustring.find(text, "==+%s*" .. H .. "%s*==+")
    if not startPos then return nil end
    -- Finde nächste Überschrift nach startPos
    local nextPos = mw.ustring.find(text, "\n==+%s*[^=\n].-==+", startPos + 1)
    local seg
    if nextPos then
        seg = mw.ustring.sub(text, startPos, nextPos - 1)
    else
        seg = mw.ustring.sub(text, startPos)
    end
    -- schneide die Überschriftenzeile selbst ab
    local body = seg:gsub("^.-\n", "")
    return body
end

-- Einleitung (vor erster Überschrift)
local function extrahiereLead(text)
	local firstH = mw.ustring.find(text, "==+")
	if firstH then
		return mw.ustring.sub(text, 1, firstH - 1)
	end
	return text
end

-- Markenspannen extrahieren ---------------------------------------------------
local function extrahiereMarke(text, label)
	label = u.trim(label)
	if label == "" then return nil end
	local L = u.esc(label)

	-- (a) VE‑Marker via Vorlagen: {{SchaufensterBegin|Label}} ... {{SchaufensterEnde|Label}}
	local function find_any(txt, patterns, init)
		for _, patt in ipairs(patterns) do
			local s, e = mw.ustring.find(txt, patt, init)
			if e then return s, e end
		end
		return nil, nil
	end

	local beginNames = { "SchauBegin", "SchaufensterBegin", "SchaufensterBeginn", "SF%-Begin" }
	local endNames   = { "SchauEnde",  "SchaufensterEnde",  "SF%-Ende" }

	local beginPats = {}
	for _, name in ipairs(beginNames) do
		table.insert(beginPats, "{{%s*" .. name .. "%s*|%s*" .. L .. "%s*}}")
	end
	local b1s, b1e = find_any(text, beginPats)
	if b1e then
		local endPats = {}
		for _, name in ipairs(endNames) do
			table.insert(endPats, "{{%s*" .. name .. "%s*|%s*" .. L .. "%s*}}")
		end
		local e2s = select(1, find_any(text, endPats, b1e + 1))
		if e2s then
			return mw.ustring.sub(text, b1e + 1, e2s - 1)
		end
	end

	-- (b) LST‑Marker: <section begin="Label"/> ... <section end="Label"/>
	do
		local lb1s, lb1e = mw.ustring.find(text, "<%s*[Ss]ection%s+[Bb]egin%s*=%s*[\"']" .. L .. "[\"']%s*/%s*>")
		if lb1e then
			local le2s = mw.ustring.find(text, "<%s*[Ss]ection%s+[Ee]nd%s*=%s*[\"']" .. L .. "[\"']%s*/%s*>", lb1e + 1)
			if le2s then
				return mw.ustring.sub(text, lb1e + 1, le2s - 1)
			end
		end
	end

	-- (c) Kommentar‑Marker (optional): <!-- BF:BEGIN Label --> ... <!-- BF:END Label -->
	do
		local cb1s, cb1e = mw.ustring.find(text, "<!%-%-%s*[BbSs][Ff]%s*:%s*[Bb][Ee][Gg][Ii][Nn]%s+" .. L .. "%s*%-%->")
		if cb1e then
			local ce2s = mw.ustring.find(text, "<!%-%-%s*[BbSs][Ff]%s*:%s*[Ee][Nn][Dd]%s+" .. L .. "%s*%-%->", cb1e + 1)
			if ce2s then
				return mw.ustring.sub(text, cb1e + 1, ce2s - 1)
			end
		end
	end

	return nil
end

-- SMW-Property-Links in normale Links umwandeln (immer anwenden)
local function stripSMWPropertyLinks(text)
    if not text or text == "" then return text end
    -- [[Property::Wert|Label]] -> [[Wert|Label]]
    text = mw.ustring.gsub(text, "%[%[%s*[%w_]+::([^|%]]+)%|([^%]]+)%]%]", "[[%1|%2]]")
    -- [[Property::Wert]] -> [[Wert]]
    text = mw.ustring.gsub(text, "%[%[%s*[%w_]+::([^%]]+)%]%]", "[[%1]]")
    return text
end

-- --- Rekursiver Expander für innere {{Schaufenster …}} ----------------------
-- Parsen einer einfachen |key=value|-Liste (dein üblicher Vorlagenstil)
local function parseTemplateArgs(paramstr)
    local a, i = {}, 1
    for part in mw.text.gsplit(paramstr or "", "|", false) do
        part = mw.text.trim(part)
        if part ~= "" then
            local k, v = part:match("^([^=]+)=(.*)$")
            if k then
                a[mw.text.trim(k)] = mw.text.trim(v)
            else
                a[i] = part; i = i + 1
            end
        end
    end
    return a
end

-- Expandiert innere {{Schaufenster …}}-Aufrufe, ohne die Vorlage erneut zu transkludieren.
-- depth: wie viele Schaufenster-Ebenen dürfen noch expandiert werden (>=0).
local function expandInnerSchaufenster(text, depth, outerFrame)
    if not text or text == "" or (depth or 0) <= 0 then
        return text
    end
    -- Achtung: einfacher Matcher – erwartet "normale" Benutzung (keine verschachtelten "{{" in Parametern)
	-- Match NUR echte {{Schaufenster|...}}-Aufrufe (Pipe nach dem Namen zwingend),
	-- vermeidet false positives bei {{SchaufensterBeginn}} / {{SchaufensterEnde}}
	local patt = "{{%s*[Ss]chaufenster%s*(|[^{}]-)}}"
	return (mw.ustring.gsub(text, patt, function(paramstr)
	    -- paramstr beginnt hier mit "|..."; führenden Pipe entfernen
	    paramstr = paramstr:gsub("^%s*|", "")
	    -- Falls verschachtelte Vorlagen im Parameter vorkommen, den Call unverändert lassen
	    if paramstr:find("{{", 1, true) then
	        return "{{Schaufenster|" .. paramstr .. "}}"
	    end
	    local a = parseTemplateArgs(paramstr)
	    local fake = {
	        args = a,
	        preprocess = function(_, w) return outerFrame:preprocess(w) end,
	        getParent = function() return nil end
	    }
        -- expandiere eine Ebene tiefer
        return p.zeige(fake, (depth or 1) - 1)
    end))
end


-- Hauptfunktion --------------------------------------------------------------
function p.zeige(frame, depth)
	depth = tonumber(depth) or 2
	local args		  = frame:getParent() and frame:getParent().args or frame.args
	local depthArg = tonumber(args.tiefe or args.depth)
		if depthArg then depth = depthArg end
	local seitentitel = u.trim(args.Seite or args.Seitenname or args[1] or "")
	local bereich     = u.trim(args.Abschnitt or args.Bereich or args[2] or "")
	local marke       = u.trim(args.Marke or args.label or "")
	local suchmodus   = (args.suche or args.modus or ""):lower()  -- "ignorieren" | "abwerten" | ""
	local ohneKat     = tostring(args.ohneKat or "1") ~= "0"
	local ohneVorlagen= tostring(args.ohneVorlagen or "1") ~= "0"
	local linkParam   = args.link
	local link        = not (linkParam == "0" or linkParam == 0 or linkParam == false)
	local refs        = tostring(args.refs or args.reflist or "0") ~= "0"
	local inline      = tostring(args.inline or args.zeilig or "0") ~= "0"
	local linkpos     = (args.linkpos or args.linkposition or "prefix"):lower()

	if seitentitel == "" then
		return "<strong class=\"error\">Schaufenster: Kein Seitentitel angegeben.</strong>"
	end

	local t = mw.title.new(seitentitel)
	if not t or not t.exists then
		-- Fehlende Seite: gib den roten Link zur Anlage/Prüfung des Titels aus (MediaWiki-Standardverhalten)
		return string.format("[[%s]]", seitentitel)
	end

	local content = t:getContent() or ""
	local debugText = ""
if debugModus then
    debugText = debugText .. string.format("[Debug] Quelle geladen: %s (%d Zeichen)\n", t.prefixedText, mw.ustring.len(content))
end


	-- 1) Vorrang: markierte Spanne
	local out
	if marke ~= "" then
		out = extrahiereMarke(content, marke) or ""
		if debugModus then debugText = debugText .. string.format("[Debug] Marke '%s' extrahiert.\n", marke) end
	end

	-- 2) Abschnitt
	if not out or out == "" then
		if bereich ~= "" then
			out = extrahiereBereich(content, bereich) or ""
			if debugModus then debugText = debugText .. string.format("[Debug] Abschnitt '%s' extrahiert.\n", bereich) end
		else
			-- 3) Fallback: ganze Seite (kein Marker/Abschnitt)
			out = content
			if debugModus then debugText = debugText .. "[Debug] Fallback: ganze Seite.\n" end
		end
	end

	-- 4) Filter anwenden
	if ohneKat then out = stripKategorien(out) end
	if ohneVorlagen then
		local namen = ladeListeVonSeite("Vorlage:Schaufenster/Vorlagenfilter")
		if #namen == 0 then namen = ladeListeVonSeite("Template:Schaufenster/Vorlagenfilter") end
		if #namen == 0 then namen = ladeListeVonSeite("MediaWiki:Vorlagenfilter") end
		out = stripVorlagen(out, namen)
	end
	-- SMW-Property-Links immer entschärfen
	out = stripSMWPropertyLinks(out)
	
	-- Innere {{Schaufenster …}} im Ergebnis vorab expandieren (verhindert Template-Selbstrekursion)
    out = expandInnerSchaufenster(out, depth, frame)

	-- 5) Ausgabe verpacken (Suchsteuerung)
	local cls
	if suchmodus == "abwerten" or suchmodus == "aux" then
		cls = "searchaux"
	elseif suchmodus == "ignorieren" or suchmodus == "exclude" or suchmodus == "" then
		cls = "navigation-not-searchable"
	else
		cls = nil
	end


-- Prefix-Link (später im Wrapper ausgeben)
local html = {}
local prefixHTML = ""
if link and linkpos == 'prefix' then
    local linkLabel
    if type(linkParam) == 'string' and linkParam ~= '' and linkParam ~= '1' then
        linkLabel = linkParam
    else
        linkLabel = '▷'
    end
    prefixHTML = string.format('[[%s|%s]]&nbsp;', t.prefixedText, linkLabel)
end

local inlineMode = inline or (link and linkpos == 'prefix')

if cls then
    -- Immer Block-Wrapper für klare VE-Grenzen
    table.insert(html, '<div class="schaufenster-content ' .. cls .. '">')
    table.insert(html, prefixHTML)
    table.insert(html, out)
    table.insert(html, '</div>\n')
else
    if inlineMode then
        -- Auch ohne Klasse einen Block-Wrapper setzen
        table.insert(html, '<div class="schaufenster-content">')
        table.insert(html, prefixHTML)
        table.insert(html, out)
        table.insert(html, '</div>\n')
    else
        -- Klassischer Blockmodus mit Absatz-Puffern
        table.insert(html, '\n' .. out .. '\n')
    end
end

-- Suffix-Link (unterhalb; außerhalb des Blocks)
if link and linkpos ~= 'prefix' then
    local linkLabel
    if type(linkParam) == 'string' and linkParam ~= '' and linkParam ~= '1' then
        linkLabel = linkParam
    else
        linkLabel = '▷ Quelle'
    end
    table.insert(html, string.format('\n<div class="schaufenster-quelle">[[%s|%s]]</div>', t.prefixedText, linkLabel))
end

if refs then
    -- refs typischerweise unterhalb; Blockgrenze ist bereits gesetzt
    table.insert(html, '\n<references responsive="1" />')
end

local wikitext = table.concat(html)


	local rendered = frame:preprocess(wikitext)

	if debugModus then
		return string.format("<pre>%s</pre>%s", debugText, rendered)
	end
	return rendered
end

return p