Перейти к содержанию

Модуль:Рецепт

Материал из Викиучебника — открытых книг для открытого мира

Для документации этого модуля может быть создана страница Модуль:Рецепт/doc

-- Module:RecipeBox
-- Инфоблок рецепта: изображение, таблица свойств, ссылки на статью в Википедии и Викискладе,
-- автоматические категории и пиктограмма сложности.
-- Приоритет данных: параметр шаблона > Wikidata.

local p = {}

--------------------------------------------------------------------
-- Свойства Wikidata (для fallback)
--------------------------------------------------------------------
local propertyMap = {
    image       = 'P18',   -- изображение
    category    = 'P8431', -- тип блюда
    cuisine     = 'P2012', -- кухня
    ingredients = 'P527',  -- состав
}

--------------------------------------------------------------------
-- Алиасы параметров (русские и позиционные)
--------------------------------------------------------------------
local paramAlias = {
    ['Изображение']='image',['5']='image',
    ['Категория']='category',['1']='category',
    ['Кухня']='cuisine',['7']='cuisine',
    ['Порций']='portions',
    ['Время']='time',
    ['Сложность']='difficulty',['4']='difficulty',
    ['Энергетическая ценность']='calories',
    ['Ингредиенты']='ingredients',['8']='ingredients',
    ['Википедия']='wikiparam',['9']='wikiparam',
    ['Викисклад']='commonsparam',['10']='commonsparam',
}

--------------------------------------------------------------------
-- Константы отображения
--------------------------------------------------------------------
local fields     = {'category','cuisine','ingredients', 'portions','time','difficulty','calories'}
local catFields  = { category=true, cuisine=true, ingredients=true }
local linkFields = { category=true, cuisine=true, ingredients=true }
local labelMap   = {
    category='Категория:', cuisine='Кухня:', portions='Порций:', time='Время:',
    difficulty='Сложность:', calories='Энергетическая ценность:', ingredients='Ингредиенты:'
}
local noFallback = { portions=true, time=true, difficulty=true, calories=true }

--------------------------------------------------------------------
-- Кэш меток Wikidata
--------------------------------------------------------------------
local labelCache = {}
local function wdLabel(id)
    if not id then return '' end
    if labelCache[id] then return labelCache[id] end
    local lbl = mw.wikibase.getLabel(id) or id
    labelCache[id] = lbl
    return lbl
end

--------------------------------------------------------------------
-- Datavalue → строка
--------------------------------------------------------------------
local function parseDV(dv)
    if not dv then return nil end
    if dv.id then return wdLabel(dv.id) end
    if dv[1] and dv[1].id then
        local t = {}
        for _, itm in ipairs(dv) do t[#t+1] = wdLabel(itm.id) end
        return table.concat(t, ', ')
    end
    if type(dv)=='string' then return dv end
    if dv.amount then return dv.amount end
    if dv.time then return dv.time end
    if dv.text then return dv.text end
    return nil
end

local function getAllWD(entity, pid)
    local claims = entity and entity.claims and entity.claims[pid]
    if not claims then return nil end
    local seen, out = {}, {}
    for _, cl in ipairs(claims) do
        local v = parseDV(cl.mainsnak.datavalue and cl.mainsnak.datavalue.value)
        if v and v~='' and not seen[v] then seen[v]=true; out[#out+1]=v end
    end
    if #out==0 then return nil end
    return table.concat(out, '<br/>')
end

--------------------------------------------------------------------
-- Ссылки на категории внутри таблицы
--------------------------------------------------------------------
local function makeCategoryLinks(raw)
    local parts = mw.text.split(raw, '<br/>')
    for i, part in ipairs(parts) do
        part = mw.text.trim(part)
        if part~='' and not part:match('%[%[') then
            parts[i] = '[[:Категория:'..part..'|'..part..']]'
        else
            parts[i] = part
        end
    end
    return table.concat(parts, '<br/>')
end

--------------------------------------------------------------------
-- Пиктограммы сложности
--------------------------------------------------------------------
local diffInfo={ ['1']={label='Очень простой',cat='Очень простой'}, ['2']={label='Простой',cat='Простой'}, ['3']={label='Средняя',cat='Средней сложности'}, ['4']={label='Сложная',cat='Сложный'}, ['5']={label='Очень сложный',cat='Очень сложный'} }
local function diffMarkup(v)
    local key = tostring(v):match('^%s*(%d)')
    local d = diffInfo[key]
    if not d then return v end
    return '[[File:'..key..'o5dots.svg|48px|'..d.label..'|link=Категория:'..d.cat..' рецепт]]'
end

--------------------------------------------------------------------
-- Изображение
--------------------------------------------------------------------
local function getImage(args, entity)
    if args.image and args.image~='' then return mw.text.trim(args.image) end
    local cl = entity and entity.claims and entity.claims[propertyMap.image]
    if cl and cl[1] and type(cl[1].mainsnak.datavalue.value)=='string' then
        return cl[1].mainsnak.datavalue.value
    end
    return ''
end

--------------------------------------------------------------------
-- Ссылка на Википедию (param > WD) с защитой при отсутствии entity
--------------------------------------------------------------------
local function getWikiParam(args)
    return args.wikiparam and mw.text.trim(args.wikiparam) or ''
end
local function getWikipediaLink(entity, args)
    local custom = getWikiParam(args)
    if custom~='' then
        return '[[File:Wikipedia-w.svg|14px]] [[w:'..custom..'|'..custom..']] в Википедии'
    end
    if not (entity and entity.id) then return nil end
    local link = mw.wikibase.getSitelink(entity.id, 'ruwiki')
    if link and link~='' then
        return '[[File:Wikipedia-w.svg|14px]] [[:w:'..link..'|'..link..']] в Википедии'
    end
    return nil
end

--------------------------------------------------------------------
-- Ссылка на Викисклад (param > WD) с защитой
--------------------------------------------------------------------
local function getCommonsParam(args)
    return args.commonsparam and mw.text.trim(args.commonsparam) or ''
end
local function getCommonsLink(entity, args)
    local custom = getCommonsParam(args)
    if custom~='' then
        return '[[File:Commons-logo.svg|14px]] [[c:'..custom..'|'..custom..']] в Викискладе'
    end
    if not (entity and entity.id) then return nil end
    local link = mw.wikibase.getSitelink(entity.id, 'commonswiki')
    if link and link~='' then
        return '[[File:Commons-logo.svg|14px]] [[:commons:'..link..'|'..link..']] в Викискладе'
    end
    return nil
end

--------------------------------------------------------------------
-- Основная функция
--------------------------------------------------------------------
function p.Reciepe(frame)
    -- Normalize args
    local raw = frame:getParent().args or {}
    local args = {}
    for k, v in pairs(raw) do
        local key = paramAlias[k] or string.lower(k)
        local trimmed = mw.text.trim(v)
        if catFields[key] and trimmed~='' then
            trimmed = string.lower(trimmed)
        end
        args[key] = trimmed
    end

    local entity = mw.wikibase.getEntityObject() or {}
    local out, present, catBuffer = {}, {}, {}

    -- Image
    local img = getImage(args, entity)
    present.image = (img~='')
    if present.image then
        out[#out+1] = '<div class="image">[[File:'..img..'|300px]]</div>'
        out[#out+1] = '<hr style="width:100%; margin:4px 0; border:none; border-top:1px solid #ccc;" />'
    end

    -- Table and collect categories
    out[#out+1] = '<table style="width:100%;">'
    for _, field in ipairs(fields) do
        local val = args[field] or ''
        if val=='' and not noFallback[field] then
            val = getAllWD(entity, propertyMap[field]) or ''
        end
        present[field] = (val~='')
        if present[field] then
            if field=='difficulty' then
                val = diffMarkup(val)
            else
                if catFields[field] then
                    local plain = val:gsub('<br/>',',')
                    for _, item in ipairs(mw.text.split(plain, ',%s*')) do
                        item = mw.text.trim(item)
                        if item~='' then catBuffer['[[Категория:'..item..']]'] = true end
                    end
                end
                val = val:gsub(',%s*','<br/>')
                if linkFields[field] then val = makeCategoryLinks(val) end
            end
            local label = labelMap[field] or (field..':')
            out[#out+1] = '<tr><td style="vertical-align:top;text-align:left;">'..label..'</td><td style="vertical-align:top;">'..val..'</td></tr>'
        end
    end
    out[#out+1] = '</table>'

    -- Wikipedia link
    local wlink = getWikipediaLink(entity, args)
    if wlink then
        out[#out+1] = '<hr style="width:100%; margin:4px 0; border:none; border-top:1px solid #ccc;" />'
        out[#out+1] = '<div style="text-align:center;">'..wlink..'</div>'
    end

    -- Commons link
    local clink = getCommonsLink(entity, args)
    if clink then
        out[#out+1] = '<div style="text-align:center;">'..clink..'</div>'
    end

    -- Technical categories
    if not present.image       then catBuffer['[[Категория:Рецепты без иллюстраций]]'] = true end
    if not present.category    then catBuffer['[[Категория:Рецепты без категории]]']      = true end
    if not present.cuisine     then catBuffer['[[Категория:Рецепты без кухни]]']            = true end
    if not present.ingredients then catBuffer['[[Категория:Рецепты без ингредиентов]]'] = true end

    -- Add collected categories
    for cat,_ in pairs(catBuffer) do out[#out+1] = cat end

    return table.concat(out, '\n')
end

return p