Zum Inhalt springen

Modul:WikibaseLink

aus AufEinWort, der freien Forschung
Vorlagenprogrammierung Diskussionen Lua Unterseiten
Modul Deutsch

Modul: Dokumentation

Diese Seite enthält Code in der Programmiersprache Lua. Einbindungszahl Cirrus


local p = {}

------------------------------------------------------------------
-- Modul:WikibaseLink – überarbeitete Fassung (sichtbare Ausgabe)
-- Ziel:
-- 1) Links zu Q / P / L ausgeben (Special:EntityPage/...)
-- 2) Für P und L passende Kurzbeschreibung ermitteln
--    - P: Wikibase-Beschreibung
--    - L: lexikalische Kategorie (Label)
-- 3) OPTIONAL: Kurzbeschreibung als {{SHORTDESC:...}} bereitstellen
--    (über p.shortdesc), aber NICHT mehr automatisch im sichtbaren
--    Seitentext ausgeben.
-- 4) Für P und L sichtbares Muster:
--      ID Label (Kurzbeschreibung)
--      z.B.  L2 Dingwort (Wortewort)
--
-- Nutzung (direkt per #invoke):
--   {{#invoke:WikibaseLink|link|entity=Q123}}
--   {{#invoke:WikibaseLink|link|P=P31}}
--   {{#invoke:WikibaseLink|link|L=L1}}
--   {{#invoke:WikibaseLink|shortdesc|P=P31|mode=P}}
--   {{#invoke:WikibaseLink|shortdesc|L=L1|mode=L}}
--   {{#invoke:WikibaseLink|linkAndShortdesc|L=L1|mode=L}}
--
-- Empfohlene Vorlage:WikibaseLink (Beispiel im Chat), die intern
--   {{#invoke:WikibaseLink|linkAndShortdesc|entity={{{1|}}}|mode={{{mode|auto}}}|label={{{label|}}}}}
-- aufruft.
--
-- Parameter (alle Funktionen):
--   |entity=  Q123 / P31 / L1 (allgemein)
--   |Q=       explizit Item
--   |P=       explizit Property
--   |L=       explizit Lexem
--   |mode=    'auto' (Standard) / 'Q' / 'P' / 'L'
--             steuert v. a. die Kurzbeschreibung.
--   |label=   optionaler Basis-Linktext statt Wikibase-Label/Lemma
--
-- Fallback:
--   Wenn keine Entity per Parameter angegeben ist, versucht
--   das Modul mw.wikibase.getEntityIdForCurrentPage().
------------------------------------------------------------------

-- Hilfsfunktion: Trim
local function trim(s)
  if type(s) ~= 'string' then
    return s
  end
  return (s:gsub('^%s+', ''):gsub('%s+$', ''))
end

-- Hilfsfunktion: Argumente nur aus dem aktuellen frame holen
-- (keine Parent-Args, damit Aufrufe wie L=L2 zuverlässig wirken)
local function getArgs(frame)
  local args = {}
  if not frame then
    return args
  end

  for k, v in pairs(frame.args or {}) do
    if v ~= '' and v ~= nil then
      args[k] = v
    end
  end

  return args
end

-- Entity-Typ bestimmen
local function getEntityType(id)
  if type(id) ~= 'string' then
    return nil
  end
  if id:match('^Q%d+$') then
    return 'item'    -- Q
  elseif id:match('^P%d+$') then
    return 'property' -- P
  elseif id:match('^L%d+$') then
    return 'lexeme'  -- L
  end
  return nil
end

local function detectEntityId(args)
  -- Kandidaten aus diesem Aufruf in fester Priorität;
  -- wir bauen die Liste ohne Nil-Werte, damit ipairs
  -- nicht durch führende Nil-Einträge vorzeitig abbricht.

  -- 1) explizite Typen (L, P, Q)
  local rawL = args.L or args.lexeme or args.l
  local rawP = args.P or args.p
  local rawQ = args.Q or args.q

  -- 2) generische entity/id
  local rawEntity = args.entity or args.Entity
  local rawId = args.id or args.ID

  -- 3) Positionsparameter (1)
  local rawPos = args[1]

  local ordered = {}
  local function push(v)
    if v ~= nil and v ~= '' then
      table.insert(ordered, v)
    end
  end

  -- Reihenfolge der Priorität
  push(rawL)
  push(rawP)
  push(rawQ)
  push(rawEntity)
  push(rawId)
  push(rawPos)

for _, v in ipairs(ordered) do
  local s = trim(tostring(v))
  if s ~= '' then
    -- Direkte ID (Q123 / P31 / L2)
    local id = s:match('^[QPL]%d+$')
    -- Oder mit Namensraum-Präfix (z.B. "Lexeme:L2", "Property:P31", "Item:Q6")
    if not id then
      id = s:match('^.-:([QPL]%d+)$')
    end
    if id then
      return id
    end
  end
end

-- Fallback: Entity zur aktuellen Seite (z. B. Q bei Sitelink)
local ok, currentId = pcall(mw.wikibase.getEntityIdForCurrentPage)
if ok and type(currentId) == 'string' and currentId:match('^[QPL]%d+$') then
  return currentId
end


  return nil
end

-- Label-/Lemma-Ermittlung für eine Entity (ohne Wikitext)
local function buildEntityLabel(entityId)
  if not entityId then
    return nil
  end

  local etype = getEntityType(entityId)

  -- Spezieller Fall: Lexeme → Lemma in Inhalts-Sprache
  if etype == 'lexeme' then
    local ok, entity = pcall(mw.wikibase.getEntity, entityId)
    if ok and type(entity) == 'table' and type(entity.lemmas) == 'table' then
      local contentLang = 'de'
      if mw.getContentLanguage then
        local cl = mw.getContentLanguage()
        if cl and cl.getCode then
          contentLang = cl:getCode()
        end
      end
      local lemma
      if entity.lemmas[contentLang] and entity.lemmas[contentLang].value then
        lemma = entity.lemmas[contentLang].value
      else
        -- Fallback: irgendein Lemma
        for _, obj in pairs(entity.lemmas) do
          if type(obj) == 'table' and obj.value then
            lemma = obj.value
            break
          end
        end
      end
      if lemma and lemma ~= '' then
        return lemma
      end
    end
  end

  -- Standard: Label über Wikibase
  local ok, fetched = pcall(mw.wikibase.getLabel, entityId)
  if ok and fetched and fetched ~= '' then
    return fetched
  end

  return entityId
end

-- Link auf eine Entity bauen (mit optionalem Label)
local function buildEntityLink(entityId, labelOverride)
  if not entityId then
    return ''
  end

  local label = labelOverride
  if not label or label == '' then
    label = buildEntityLabel(entityId) or entityId
  end

  return string.format('[[Special:EntityPage/%s|%s]]', entityId, label)
end


-- Kurzbeschreibung für eine Entity bestimmen
-- mode:
--   'auto' / nil ->
--       Q (item)      → Wikibase-Beschreibung
--       P (property)  → Wikibase-Beschreibung
--       L (lexeme)    → Label der lexikalischen Kategorie
--   'Q' / 'item'      → Wikibase-Beschreibung
--   'P' / 'property'  → Wikibase-Beschreibung
--   'L' / 'lexeme'    → Label der lexikalischen Kategorie
local function buildShortdescText(entityId, mode)
  if not entityId then
    return nil
  end

  local etype = getEntityType(entityId)
  if not etype then
    return nil
  end

  mode = mode and tostring(mode):lower() or 'auto'

  if mode == 'auto' then
    if etype == 'item' then
      mode = 'q'
    elseif etype == 'property' then
      mode = 'p'
    elseif etype == 'lexeme' then
      mode = 'l'
    end
  end

  -- Q und P: Wikibase-Beschreibung verwenden
  if mode == 'q' or mode == 'item'
    or mode == 'p' or mode == 'property'
  then
    local ok, desc = pcall( mw.wikibase.getDescription, entityId )
    if ok and type( desc ) == 'string' and desc ~= '' then
      return desc
    end
    return nil

  elseif mode == 'l' or mode == 'lexeme' then
    -- Lexem: lexikalische Kategorie → Label dieser Kategorie
    local ok, entity = pcall( mw.wikibase.getEntity, entityId )
    if not ok or type( entity ) ~= 'table' then
      return nil
    end

    local lc = entity.lexicalCategory
    if type( lc ) == 'table' then
      lc = lc.id or lc.value
    end

    if type( lc ) ~= 'string' or lc == '' then
      return nil
    end

    local okLabel, label = pcall( mw.wikibase.getLabel, lc )
    if okLabel and type( label ) == 'string' and label ~= '' then
      return label
    else
      return lc
    end
  end

  return nil
end

-- Gloss für einen Lexem-Sinn ermitteln (z.B. S=1, S='S1', S='L1-S1')
local function getLexemeSenseGloss(entityId, senseParam)
  if not entityId or not senseParam or senseParam == '' then
    return nil
  end

  local etype = getEntityType(entityId)
  if etype ~= 'lexeme' then
    return nil
  end

  -- Sense-ID kanonisieren
  local s = tostring(senseParam)
  local senseId

  -- Vollständige ID: L123-S4
  if s:match('^L%d+-S%d+$') then
    senseId = s
  else
    -- „S4“ oder „4“ → L123-S4
    local num = s:match('^S(%d+)$') or s:match('^(%d+)$')
    if not num then
      return nil
    end
    senseId = string.format('%s-S%s', entityId, num)
  end

  -- Lexem-Entity holen
  local ok, entity = pcall( mw.wikibase.getEntity, entityId )
  if not ok or type(entity) ~= 'table' or type(entity.senses) ~= 'table' then
    return nil
  end

  -- Ziel-Sense in entity.senses suchen
  local targetSense = nil
  for key, sense in pairs(entity.senses) do
    if type(sense) == 'table' then
      local sid = sense.id or key
      if sid == senseId then
        targetSense = sense
        break
      end
    end
  end

  if not targetSense or type(targetSense.glosses) ~= 'table' then
    return nil
  end

  -- Inhalts-Sprache bestimmen
  local contentLang = 'de'
  if mw.getContentLanguage then
    local cl = mw.getContentLanguage()
    if cl and cl.getCode then
      contentLang = cl:getCode()
    end
  end

  -- Gloss in Inhalts-Sprache bevorzugen, sonst irgendeinen
  local gloss
  local g = targetSense.glosses[contentLang]
  if g and type(g) == 'table' and g.value then
    gloss = g.value
  else
    for _, obj in pairs(targetSense.glosses) do
      if type(obj) == 'table' and obj.value then
        gloss = obj.value
        break
      end
    end
  end

  if gloss and gloss ~= '' then
    return gloss
  end

  return nil
end

-- Form (Wortform) für ein Lexem ermitteln (z.B. F=1, F='F1', F='L2-F1')
local function getLexemeFormRepresentation(entityId, formParam)
  if not entityId or not formParam or formParam == '' then
    return nil
  end

  local etype = getEntityType(entityId)
  if etype ~= 'lexeme' then
    return nil
  end

  -- Form-ID kanonisieren
  local s = tostring(formParam)
  local formId

  -- Vollständige ID: L123-F4
  if s:match('^L%d+-F%d+$') then
    formId = s
  else
    -- „F4“ oder „4“ → L123-F4
    local num = s:match('^F(%d+)$') or s:match('^(%d+)$')
    if not num then
      return nil
    end
    formId = string.format('%s-F%s', entityId, num)
  end

  -- Lexem-Entity holen
  local ok, entity = pcall( mw.wikibase.getEntity, entityId )
  if not ok or type(entity) ~= 'table' or type(entity.forms) ~= 'table' then
    return nil
  end

  -- Ziel-Form in entity.forms suchen
  local targetForm = nil
  for key, form in pairs(entity.forms) do
    if type(form) == 'table' then
      local fid = form.id or key
      if fid == formId then
        targetForm = form
        break
      end
    end
  end

  if not targetForm or type(targetForm.representations) ~= 'table' then
    return nil
  end

  -- Inhalts-Sprache bestimmen
  local contentLang = 'de'
  if mw.getContentLanguage then
    local cl = mw.getContentLanguage()
    if cl and cl.getCode then
      contentLang = cl:getCode()
    end
  end

  -- Wortform in Inhalts-Sprache bevorzugen, sonst irgendeine
  local formText
  local r = targetForm.representations[contentLang]
  if r and type(r) == 'table' and r.value then
    formText = r.value
  else
    for _, obj in pairs(targetForm.representations) do
      if type(obj) == 'table' and obj.value then
        formText = obj.value
        break
      end
    end
  end

  if formText and formText ~= '' then
    return formText
  end

  return nil
end


------------------------------------------------------------------
-- Öffentliche Funktionen
------------------------------------------------------------------

-- 1) Nur Link ausgeben (ohne sichtbaren Untertitel)
--    Q:  Label
--    P/L: ID Label
function p.link(frame)
  local args = getArgs(frame)
  local entityId = detectEntityId(args)
  if not entityId then
    return ''
  end

  local etype = getEntityType(entityId)
  local baseLabel = args.label or args.text or buildEntityLabel(entityId) or entityId

  local linkLabel
  if etype == 'item' then
    -- Q: nur das Label
    linkLabel = baseLabel
  else
    -- P/L: ID + Label
    linkLabel = string.format('%s %s', entityId, baseLabel)
  end

  -- Sicherheit: keine Zeilenumbrüche im Linktext
  linkLabel = trim(linkLabel):gsub('\n', ' ')

  return buildEntityLink(entityId, linkLabel)
end

-- 2) Nur SHORTDESC-Wikitext zurückgeben (falls sinnvoll)
--    Hinweis: Ob die ShortDescription-Extension {{SHORTDESC:...}}
--    aus Vorlagen heraus verarbeitet, hängt von der lokalen
--    Konfiguration ab.
function p.shortdesc(frame)
  local args = getArgs(frame)
  local entityId = detectEntityId(args)
  if not entityId then
    return ''
  end

  local mode = args.mode or args.typ or args.type
  local text = buildShortdescText(entityId, mode)
  if not text or text == '' then
    return ''
  end

  text = trim(text):gsub('\n', ' ')

  -- SHORTDESC als Parserfunktion aufrufen, so als stünde
  -- {{SHORTDESC:text}} im Quelltext.
  local ok, result = pcall(function()
    -- Entspricht {{SHORTDESC:text}}
    return frame:callParserFunction('SHORTDESC', text)
  end)

  if not ok then
    -- Falls die Parserfunktion nicht verfügbar ist, lieber
    -- still scheitern, damit Seiten nicht kaputtgehen.
    return ''
  end

  -- SHORTDESC gibt in der Regel nichts Sichtbares zurück.
  -- Wir geben das Ergebnis (typisch leer) zurück, so dass
  -- der #invoke-Aufruf im Seitentext nichts anzeigt.
  return result or ''
end


-- 3) Link mit Untertitel kombinieren
--    Muster:
--       Lexem ohne Sinn/Form:   [[.../L2|L2 Dingwort]] (Wortewort)
--       Lexem mit Sinn (S):     [[.../L2|L2 Dingwort]] (Wortewort)
--                                Ding benennendes Wort
--       Lexem mit Sinn + Form:  [[.../L2|L2 Dingwort]] (Wortewort)
--                                Ding benennendes Wort
--                                Ding benennende Wörter  (z.B. F1)
--       Item/Property:          [[.../Q6|Q6 Dingwort]] (…Beschreibung…)
function p.subtitle(frame)
  local args = getArgs(frame)
  local entityId = detectEntityId(args)
  if not entityId then
    return ''
  end

  local mode  = args.mode or args.typ or args.type
  local etype = getEntityType(entityId)

  -- Basislabel: explizit oder aus Wikibase/Lexem-Lemma
  local baseLabel = args.label or args.text or buildEntityLabel(entityId) or entityId

  -- Untertitel: Beschreibung (Q/P) bzw. Wortart (L)
  local desc = buildShortdescText(entityId, mode)
  if desc ~= nil then
    desc = trim(desc):gsub('\n', ' ')
  end

  ----------------------------------------------------------------
  -- Sinn (Sense) für Lexeme optional bestimmen (S=…)
  ----------------------------------------------------------------
  local senseParam = args.S or args.s or args.sense
  local senseGloss = nil
  if senseParam and etype == 'lexeme' then
    senseGloss = getLexemeSenseGloss(entityId, senseParam)
    if senseGloss ~= nil then
      senseGloss = trim(senseGloss):gsub('\n', ' ')
    end
  end

  ----------------------------------------------------------------
  -- Form (Wortform) für Lexeme optional bestimmen (F=…)
  ----------------------------------------------------------------
  local formParam = args.F or args.f or args.form or args.Form
  local formText = nil
  if formParam and etype == 'lexeme' then
    formText = getLexemeFormRepresentation(entityId, formParam)
    if formText ~= nil then
      formText = trim(formText):gsub('\n', ' ')
    end
  end

  ----------------------------------------------------------------
  -- Steuerung, ob der Untertitel im sichtbaren Text stehen soll
  -- subtitle: ja/1 (Standard) → sichtbar
  --           nein/0          → unsichtbar
  ----------------------------------------------------------------
  local subtitleParam = args.subtitle
  local showSubtitle  = true  -- Default: ja

  if subtitleParam ~= nil and subtitleParam ~= '' then
    local s = tostring(subtitleParam):lower()
    if s == 'nein' or s == '0' then
      showSubtitle = false
    elseif s == 'ja' or s == '1' then
      showSubtitle = true
    end
  end

  -- SHORTDESC als Seiteneigenschaft setzen (für Suche), falls vorhanden
  -- (weiterhin nur aus der Kurzbeschreibung, nicht aus Sinn/Form)
  if desc and desc ~= '' then
    pcall(function()
      frame:callParserFunction('SHORTDESC', desc)
    end)
  end

  -- Sichtbarer Link-Teil: für Q/P/L immer "ID Label"
  local visibleLabel = string.format('%s %s', entityId, baseLabel)
  visibleLabel = trim(visibleLabel):gsub('\n', ' ')

  -- Link nur um ID + Label
  local linkText = buildEntityLink(entityId, visibleLabel)

  -- Wenn keine sichtbaren Untertitel gewünscht → nur Link zurückgeben
  if not showSubtitle then
    return linkText
  end

  -- Spezielles Layout für Lexeme: Zeile 1 + optional Zeile 2/3
  if etype == 'lexeme' then
    local line1
    if desc and desc ~= '' then
      line1 = string.format('%s (%s)', linkText, desc)
    else
      line1 = linkText
    end

    local lines = { line1 }

    if senseGloss and senseGloss ~= '' then
      table.insert(lines, senseGloss)
    end

    if formText and formText ~= '' then
      table.insert(lines, formText)
    end

    -- Zeilen mit <br /> verbinden
    return table.concat(lines, '<br />')
  end

  -- Standardfall: Q / P → Link + (Beschreibung)
  if desc and desc ~= '' then
    return string.format('%s (%s)', linkText, desc)
  else
    return linkText
  end
end


return p