پودمان:AlborzEducation: تفاوت میان نسخهها
ظاهر
جزبدون خلاصۀ ویرایش |
جزبدون خلاصۀ ویرایش |
||
| (۱۲ نسخهٔ میانیِ ایجادشده توسط همین کاربر نشان داده نشد) | |||
| خط ۲: | خط ۲: | ||
-- Module:AlborzEducation | -- Module:AlborzEducation | ||
-- Wiki Alborz Education Infobox Engine | -- Wiki Alborz Education Infobox Engine | ||
-- | -- نسخهٔ نهایی با تشخیص هوشمند و مقاوم layout برای آموزشگاه هنری | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
local p = {} | local p = {} | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- | -- CONFIGURATION | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
local INFOBOX_CLASS = "infobox education-infobox" | local INFOBOX_CLASS = "infobox education-infobox" | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- LOAD DATA | -- LOAD DATA | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
local data = mw.loadData("Module:AlborzEducation/Data") | |||
local data = mw.loadData( | |||
) | |||
local lookup = data.lookup | local lookup = data.lookup | ||
local fields = data.fieldDefinitions | local fields = data.fieldDefinitions | ||
local layouts = data.layouts | local layouts = data.layouts | ||
local groups = data.groups | local groups = data.groups | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- BASIC UTILITIES | -- BASIC UTILITIES | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
local function trim(value) | local function trim(value) | ||
if value == nil then | |||
if | |||
return nil | return nil | ||
end | end | ||
value = mw.text.trim(tostring(value)) | |||
value = mw.text.trim(value) | |||
if value == "" then | if value == "" then | ||
return nil | return nil | ||
end | end | ||
return value | return value | ||
end | end | ||
local function getArgs(frame) | local function getArgs(frame) | ||
local args = {} | local args = {} | ||
for k, v in pairs(frame.args) do | |||
k = trim(k) | |||
for | v = trim(v) | ||
if k and v then | |||
args[k] = v | |||
if | |||
args[ | |||
end | end | ||
end | end | ||
return args | return args | ||
end | end | ||
local function normalizeArgs(args) | local function normalizeArgs(args) | ||
local result = {} | local result = {} | ||
for key, value in pairs(args) do | for key, value in pairs(args) do | ||
local newKey = lookup[key] or key | |||
result[newKey] = value | |||
local | |||
end | end | ||
return result | return result | ||
end | |||
local function getFieldValue(args, name) | |||
if args[name] then | |||
return args[name] | |||
end | |||
local def = fields[name] | |||
if def and def.default then | |||
return def.default | |||
end | |||
return nil | |||
end | end | ||
local function isAlreadyFormatted(value) | |||
return value:match("^{{") or value:match("^%[") or value:match("^%[%[") | |||
end | |||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- | -- IMAGE HELPER | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
local function imageMarkup(file, width) | |||
local function | if not file then | ||
return nil | |||
if | |||
return | |||
end | end | ||
width = width or "250px" | |||
return string.format("[[پرونده:%s|%s|center]]", file, width) | |||
return | |||
end | end | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- | -- URL & ID HELPERS | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
local function normalizeUrl(url) | |||
local function | url = trim(url) | ||
if not url then | |||
if not | |||
return nil | return nil | ||
end | end | ||
if not url:match("^https?://") then | |||
url = "https://" .. url | |||
end | |||
return url | |||
end | |||
local function extractId(value) | |||
value = trim(value) | |||
if not value then | if not value then | ||
return nil | return nil | ||
end | end | ||
value = value:gsub("^https?://", "") | |||
value = value:gsub("^www%.", "") | |||
value = value:gsub("^instagram%.com/", "") | |||
value = value:gsub("^t%.me/", "") | |||
value = value:gsub("^aparat%.com/", "") | |||
value = value:gsub("^@", "") | |||
value = value:gsub("/$", "") | |||
return value | |||
end | |||
-------------------------------------------------------------------------------- | |||
-- BOOLEAN | |||
-------------------------------------------------------------------------------- | |||
local function formatBoolean(value) | |||
local yes = { | |||
["بله"] = true, ["بلی"] = true, | |||
["yes"] = true, ["Yes"] = true, ["YES"] = true, | |||
["true"] = true, ["True"] = true, ["TRUE"] = true, | |||
["1"] = true | |||
} | |||
if yes[tostring(value)] then | |||
return "دارد" | |||
end | |||
return "ندارد" | |||
end | |||
-------------------------------------------------------------------------------- | |||
-- LIST HELPER | |||
-------------------------------------------------------------------------------- | |||
local function formatList(value) | |||
if isAlreadyFormatted(value) then | |||
return value | |||
return | |||
end | end | ||
value = value:gsub("،", ",") | |||
local items = {} | |||
for item in mw.text.gsplit(value, ",") do | |||
item = trim(item) | |||
if item and item ~= "" then | |||
table.insert(items, "* " .. item) | |||
end | end | ||
end | end | ||
if #items == 0 then | |||
return value | |||
if | |||
value | |||
end | end | ||
return table.concat(items, "\n") | |||
end | end | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- | -- FORMATTERS | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
local formatters = { | |||
website = function(value) | |||
if isAlreadyFormatted(value) then | |||
return value | |||
end | |||
local url = normalizeUrl(value) | |||
return string.format("[%s وبگاه رسمی]", url) | |||
end, | |||
email = function(value) | |||
if isAlreadyFormatted(value) then | |||
return value | |||
end | |||
return string.format("[mailto:%s %s]", value, value) | |||
end, | |||
instagram = function(value) | |||
if isAlreadyFormatted(value) then | |||
return value | |||
end | |||
local id = extractId(value) | |||
return string.format("[https://instagram.com/%s @%s]", id, id) | |||
end, | |||
telegram = function(value) | |||
if isAlreadyFormatted(value) then | |||
return value | |||
end | |||
local id = extractId(value) | |||
return string.format("[https://t.me/%s @%s]", id, id) | |||
end, | |||
aparat = function(value) | |||
if isAlreadyFormatted(value) then | |||
return value | |||
end | |||
local id = extractId(value) | |||
return string.format("[https://www.aparat.com/%s صفحهٔ آپارات]", id) | |||
end, | |||
wikipedia = function(value) | |||
if isAlreadyFormatted(value) then | |||
return value | |||
end | |||
return string.format("[[%s]]", value) | |||
end, | |||
url = function(value) | |||
if isAlreadyFormatted(value) then | |||
return value | |||
end | |||
local url = normalizeUrl(value) | |||
return string.format("[%s %s]", url, value) | |||
end, | |||
boolean = function(value) | |||
return formatBoolean(value) | |||
end, | |||
list = function(value) | |||
return formatList(value) | |||
end, | |||
person = function(value) | |||
if isAlreadyFormatted(value) then | |||
return value | |||
end | |||
return string.format("[[%s]]", value) | |||
end, | |||
location = function(value) | |||
if isAlreadyFormatted(value) then | |||
return value | |||
end | |||
return string.format("[[%s]]", value) | |||
end, | |||
coordinates = function(value) | |||
if isAlreadyFormatted(value) then | |||
return value | |||
end | |||
return value | |||
end, | |||
} | |||
local function | local function formatValue(fieldName, value, fieldDef) | ||
if not value then | |||
if not | |||
return nil | return nil | ||
end | end | ||
local formatterName = (fieldDef and fieldDef.formatter) or fieldName | |||
if fieldDef and fieldDef.type and not formatters[formatterName] then | |||
if fieldDef.type == "boolean" then | |||
local | formatterName = "boolean" | ||
elseif fieldDef.type == "email" then | |||
formatterName = "email" | |||
elseif fieldDef.type == "url" then | |||
formatterName = "url" | |||
elseif fieldDef.type == "list" then | |||
formatterName = "list" | |||
end | |||
end | |||
local formatter = formatters[formatterName] | |||
if formatter then | |||
return formatter(value) | |||
return | end | ||
return value | |||
end | end | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- | -- FIELD RENDERING | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
local function renderField(fieldName, value) | |||
local function | local field = fields[fieldName] | ||
if not field or not value then | |||
local | |||
if not | |||
return nil | return nil | ||
end | end | ||
if field.type == "image" then | |||
local width = (fieldName == "logo") and "120px" or "250px" | |||
return mw.html.create("tr") | |||
:tag("td") | |||
:attr("colspan", "2") | |||
:addClass("education-infobox-image") | |||
:css("text-align", "center") | |||
:wikitext(imageMarkup(value, width)) | |||
:done() | |||
end | |||
value = formatValue(fieldName, value, field) | |||
local row = mw.html.create("tr") | local row = mw.html.create("tr") | ||
row:tag("th"):wikitext(field.label or fieldName) | |||
row:tag("td"):wikitext(value) | |||
row | |||
return row | return row | ||
end | end | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- | -- TITLE & LOGO | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
local function renderTitle(box, args) | |||
local logo = getFieldValue(args, "logo") | |||
local title = getFieldValue(args, "fullname") or getFieldValue(args, "name") | |||
if logo then | |||
box:tag("tr") | |||
:tag("td") | |||
:attr("colspan", "2") | |||
:addClass("education-infobox-logo") | |||
:css("text-align", "center") | |||
:css("padding", "8px") | |||
:wikitext(imageMarkup(logo, "120px")) | |||
end | |||
if title then | |||
box:tag("tr") | |||
:tag("th") | |||
:attr("colspan", "2") | |||
:addClass("education-infobox-title") | |||
:css("text-align", "center") | |||
:css("font-size", "120%") | |||
:css("font-weight", "bold") | |||
:css("padding", "8px") | |||
:wikitext(title) | |||
end | |||
end | |||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- | -- MAIN IMAGE + CAPTION | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
local function renderImage(box, args) | |||
local function | local image = getFieldValue(args, "image") | ||
if not image then | |||
local | |||
if not | |||
return | return | ||
end | end | ||
box:tag("tr") | |||
-------------------------------------------------------------------------- | :tag("td") | ||
:attr("colspan", "2") | |||
:addClass("education-infobox-image") | |||
:css("text-align", "center") | |||
local | :wikitext(imageMarkup(image, "250px")) | ||
local caption = getFieldValue(args, "caption") | |||
for _, | if caption then | ||
if getFieldValue(args, | box:tag("tr") | ||
:tag("td") | |||
:attr("colspan", "2") | |||
:addClass("education-infobox-caption") | |||
:css("text-align", "center") | |||
:css("font-size", "90%") | |||
:css("padding", "4px 8px") | |||
:wikitext(caption) | |||
end | |||
end | |||
-------------------------------------------------------------------------------- | |||
-- GROUP | |||
-------------------------------------------------------------------------------- | |||
local function renderGroup(box, groupName, fieldList, args, rendered) | |||
local hasData = false | |||
for _, field in ipairs(fieldList) do | |||
if getFieldValue(args, field) and not rendered[field] then | |||
hasData = true | |||
break | break | ||
end | end | ||
end | end | ||
if not hasData then | |||
if not | |||
return | return | ||
end | end | ||
local group = groups[groupName] | |||
if group then | |||
box:tag("tr") | |||
:tag("th") | |||
:attr("colspan", "2") | |||
:addClass("education-infobox-group") | |||
:css("text-align", "center") | |||
:css("font-weight", "bold") | |||
:css("padding", "6px") | |||
:wikitext((group.icon or "") .. " " .. (group.label or groupName)) | |||
end | |||
for _, field in ipairs(fieldList) do | |||
if not rendered[field] then | |||
local value = getFieldValue(args, field) | |||
if value then | |||
local row = renderField(field, value) | |||
if row then | |||
for _, | box:node(row) | ||
rendered[field] = true | |||
end | |||
end | end | ||
end | end | ||
end | end | ||
end | end | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- | -- تشخیص هوشمند layout (نسخهٔ قوی و مقاوم) | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
local function detectLayout(typeValue) | |||
if not typeValue or typeValue == "" then | |||
return "school" | |||
end | |||
local | -- نرمالسازی قوی | ||
local t = mw.ustring.lower(typeValue) | |||
t = t:gsub("", "") -- حذف نیمفاصله | |||
t = t:gsub("%s+", "") -- حذف همه فاصلهها | |||
t = t:gsub("ي", "ی") -- یکسانسازی ی | |||
t = t:gsub("ك", "ک") -- یکسانسازی ک | |||
-- مطابقت دقیق بعد از نرمالسازی | |||
local normalizedMap = { | |||
["دانشگاه"] = "university", | |||
["university"] = "university", | |||
["حوزهعلمیه"] = "seminary", | |||
["seminary"] = "seminary", | |||
["آموزشگاه"] = "training_center", | |||
["training_center"] = "training_center", | |||
["آموزشگاههنری"] = "art_school", | |||
["آموزشگاههنر"] = "art_school", | |||
["هنری"] = "art_school", | |||
["art_school"] = "art_school", | |||
["آکادمیهنری"] = "art_school", | |||
["آکادمیهنر"] = "art_school", | |||
["پژوهشکده"] = "research_center", | |||
["research_center"] = "research_center", | |||
["کالج"] = "college", | |||
["college"] = "college", | |||
["مهدکودک"] = "kindergarten", | |||
["kindergarten"] = "kindergarten", | |||
["مدرسه"] = "school", | |||
["school"] = "school", | |||
} | |||
if normalizedMap[t] then | |||
return normalizedMap[t] | |||
end | |||
-- بررسی کلمات کلیدی | |||
if | if t:find("هنری") or t:find("هنر") or t:find("آکادمی") then | ||
return | return "art_school" | ||
end | |||
if t:find("دانشگاه") then | |||
return "university" | |||
end | |||
if t:find("حوزه") or t:find("علمیه") then | |||
return "seminary" | |||
end | |||
if t:find("پژوهش") then | |||
return "research_center" | |||
end | |||
if t:find("کالج") then | |||
return "college" | |||
end | |||
if t:find("مهد") or t:find("کودک") then | |||
return "kindergarten" | |||
end | |||
if t:find("آموزشگاه") or t:find("مرکزآموزشی") or t:find("آموزشی") then | |||
return "training_center" | |||
end | |||
if t:find("مدرسه") then | |||
return "school" | |||
end | end | ||
return "school" | |||
end | end | ||
-------------------------------------------------------------------------------- | |||
-- RESOLVE INHERIT | |||
-------------------------------------------------------------------------------- | |||
local function resolveLayout(layoutName) | |||
local seen = {} | |||
local current = layoutName | |||
while current do | |||
if seen[current] then | |||
return layouts.school or {} | |||
end | |||
seen[current] = true | |||
local layout = layouts[current] | |||
if not layout then | |||
return layouts.school or {} | |||
end | |||
if layout.inherit then | |||
current = layout.inherit | |||
else | |||
return layout | |||
end | |||
end | |||
return layouts.school or {} | |||
end | end | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- | -- RENDER LAYOUT | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
local function renderLayout(layoutName, args) | |||
function | local layout = resolveLayout(layoutName) | ||
local box = mw.html.create("table") | |||
:addClass(INFOBOX_CLASS) | |||
local | :addClass("education-" .. layoutName) | ||
local rendered = {} | |||
renderTitle(box, args) | |||
rendered.logo = true | |||
local | renderImage(box, args) | ||
rendered.image = true | |||
rendered.caption = true | |||
for _, section in ipairs(layout) do | |||
renderGroup(box, section.group, section.fields, args, rendered) | |||
local | |||
end | end | ||
return tostring(box) | |||
return | |||
end | end | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- | -- MAIN | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
function p.main(frame) | |||
local args = normalizeArgs(getArgs(frame)) | |||
local layout = detectLayout(args.type) | |||
return renderLayout(layout, args) | |||
end | |||
return p | return p | ||
نسخهٔ کنونی تا ۲۵ اوت ۲۰۲۶، ساعت ۲۱:۵۴
توضیحات این پودمان میتواند در پودمان:AlborzEducation/توضیحات قرار گیرد.
--------------------------------------------------------------------------------
-- Module:AlborzEducation
-- Wiki Alborz Education Infobox Engine
-- نسخهٔ نهایی با تشخیص هوشمند و مقاوم layout برای آموزشگاه هنری
--------------------------------------------------------------------------------
local p = {}
--------------------------------------------------------------------------------
-- CONFIGURATION
--------------------------------------------------------------------------------
local INFOBOX_CLASS = "infobox education-infobox"
--------------------------------------------------------------------------------
-- LOAD DATA
--------------------------------------------------------------------------------
local data = mw.loadData("Module:AlborzEducation/Data")
local lookup = data.lookup
local fields = data.fieldDefinitions
local layouts = data.layouts
local groups = data.groups
--------------------------------------------------------------------------------
-- BASIC UTILITIES
--------------------------------------------------------------------------------
local function trim(value)
if value == nil then
return nil
end
value = mw.text.trim(tostring(value))
if value == "" then
return nil
end
return value
end
local function getArgs(frame)
local args = {}
for k, v in pairs(frame.args) do
k = trim(k)
v = trim(v)
if k and v then
args[k] = v
end
end
return args
end
local function normalizeArgs(args)
local result = {}
for key, value in pairs(args) do
local newKey = lookup[key] or key
result[newKey] = value
end
return result
end
local function getFieldValue(args, name)
if args[name] then
return args[name]
end
local def = fields[name]
if def and def.default then
return def.default
end
return nil
end
local function isAlreadyFormatted(value)
return value:match("^{{") or value:match("^%[") or value:match("^%[%[")
end
--------------------------------------------------------------------------------
-- IMAGE HELPER
--------------------------------------------------------------------------------
local function imageMarkup(file, width)
if not file then
return nil
end
width = width or "250px"
return string.format("[[پرونده:%s|%s|center]]", file, width)
end
--------------------------------------------------------------------------------
-- URL & ID HELPERS
--------------------------------------------------------------------------------
local function normalizeUrl(url)
url = trim(url)
if not url then
return nil
end
if not url:match("^https?://") then
url = "https://" .. url
end
return url
end
local function extractId(value)
value = trim(value)
if not value then
return nil
end
value = value:gsub("^https?://", "")
value = value:gsub("^www%.", "")
value = value:gsub("^instagram%.com/", "")
value = value:gsub("^t%.me/", "")
value = value:gsub("^aparat%.com/", "")
value = value:gsub("^@", "")
value = value:gsub("/$", "")
return value
end
--------------------------------------------------------------------------------
-- BOOLEAN
--------------------------------------------------------------------------------
local function formatBoolean(value)
local yes = {
["بله"] = true, ["بلی"] = true,
["yes"] = true, ["Yes"] = true, ["YES"] = true,
["true"] = true, ["True"] = true, ["TRUE"] = true,
["1"] = true
}
if yes[tostring(value)] then
return "دارد"
end
return "ندارد"
end
--------------------------------------------------------------------------------
-- LIST HELPER
--------------------------------------------------------------------------------
local function formatList(value)
if isAlreadyFormatted(value) then
return value
end
value = value:gsub("،", ",")
local items = {}
for item in mw.text.gsplit(value, ",") do
item = trim(item)
if item and item ~= "" then
table.insert(items, "* " .. item)
end
end
if #items == 0 then
return value
end
return table.concat(items, "\n")
end
--------------------------------------------------------------------------------
-- FORMATTERS
--------------------------------------------------------------------------------
local formatters = {
website = function(value)
if isAlreadyFormatted(value) then
return value
end
local url = normalizeUrl(value)
return string.format("[%s وبگاه رسمی]", url)
end,
email = function(value)
if isAlreadyFormatted(value) then
return value
end
return string.format("[mailto:%s %s]", value, value)
end,
instagram = function(value)
if isAlreadyFormatted(value) then
return value
end
local id = extractId(value)
return string.format("[https://instagram.com/%s @%s]", id, id)
end,
telegram = function(value)
if isAlreadyFormatted(value) then
return value
end
local id = extractId(value)
return string.format("[https://t.me/%s @%s]", id, id)
end,
aparat = function(value)
if isAlreadyFormatted(value) then
return value
end
local id = extractId(value)
return string.format("[https://www.aparat.com/%s صفحهٔ آپارات]", id)
end,
wikipedia = function(value)
if isAlreadyFormatted(value) then
return value
end
return string.format("[[%s]]", value)
end,
url = function(value)
if isAlreadyFormatted(value) then
return value
end
local url = normalizeUrl(value)
return string.format("[%s %s]", url, value)
end,
boolean = function(value)
return formatBoolean(value)
end,
list = function(value)
return formatList(value)
end,
person = function(value)
if isAlreadyFormatted(value) then
return value
end
return string.format("[[%s]]", value)
end,
location = function(value)
if isAlreadyFormatted(value) then
return value
end
return string.format("[[%s]]", value)
end,
coordinates = function(value)
if isAlreadyFormatted(value) then
return value
end
return value
end,
}
local function formatValue(fieldName, value, fieldDef)
if not value then
return nil
end
local formatterName = (fieldDef and fieldDef.formatter) or fieldName
if fieldDef and fieldDef.type and not formatters[formatterName] then
if fieldDef.type == "boolean" then
formatterName = "boolean"
elseif fieldDef.type == "email" then
formatterName = "email"
elseif fieldDef.type == "url" then
formatterName = "url"
elseif fieldDef.type == "list" then
formatterName = "list"
end
end
local formatter = formatters[formatterName]
if formatter then
return formatter(value)
end
return value
end
--------------------------------------------------------------------------------
-- FIELD RENDERING
--------------------------------------------------------------------------------
local function renderField(fieldName, value)
local field = fields[fieldName]
if not field or not value then
return nil
end
if field.type == "image" then
local width = (fieldName == "logo") and "120px" or "250px"
return mw.html.create("tr")
:tag("td")
:attr("colspan", "2")
:addClass("education-infobox-image")
:css("text-align", "center")
:wikitext(imageMarkup(value, width))
:done()
end
value = formatValue(fieldName, value, field)
local row = mw.html.create("tr")
row:tag("th"):wikitext(field.label or fieldName)
row:tag("td"):wikitext(value)
return row
end
--------------------------------------------------------------------------------
-- TITLE & LOGO
--------------------------------------------------------------------------------
local function renderTitle(box, args)
local logo = getFieldValue(args, "logo")
local title = getFieldValue(args, "fullname") or getFieldValue(args, "name")
if logo then
box:tag("tr")
:tag("td")
:attr("colspan", "2")
:addClass("education-infobox-logo")
:css("text-align", "center")
:css("padding", "8px")
:wikitext(imageMarkup(logo, "120px"))
end
if title then
box:tag("tr")
:tag("th")
:attr("colspan", "2")
:addClass("education-infobox-title")
:css("text-align", "center")
:css("font-size", "120%")
:css("font-weight", "bold")
:css("padding", "8px")
:wikitext(title)
end
end
--------------------------------------------------------------------------------
-- MAIN IMAGE + CAPTION
--------------------------------------------------------------------------------
local function renderImage(box, args)
local image = getFieldValue(args, "image")
if not image then
return
end
box:tag("tr")
:tag("td")
:attr("colspan", "2")
:addClass("education-infobox-image")
:css("text-align", "center")
:wikitext(imageMarkup(image, "250px"))
local caption = getFieldValue(args, "caption")
if caption then
box:tag("tr")
:tag("td")
:attr("colspan", "2")
:addClass("education-infobox-caption")
:css("text-align", "center")
:css("font-size", "90%")
:css("padding", "4px 8px")
:wikitext(caption)
end
end
--------------------------------------------------------------------------------
-- GROUP
--------------------------------------------------------------------------------
local function renderGroup(box, groupName, fieldList, args, rendered)
local hasData = false
for _, field in ipairs(fieldList) do
if getFieldValue(args, field) and not rendered[field] then
hasData = true
break
end
end
if not hasData then
return
end
local group = groups[groupName]
if group then
box:tag("tr")
:tag("th")
:attr("colspan", "2")
:addClass("education-infobox-group")
:css("text-align", "center")
:css("font-weight", "bold")
:css("padding", "6px")
:wikitext((group.icon or "") .. " " .. (group.label or groupName))
end
for _, field in ipairs(fieldList) do
if not rendered[field] then
local value = getFieldValue(args, field)
if value then
local row = renderField(field, value)
if row then
box:node(row)
rendered[field] = true
end
end
end
end
end
--------------------------------------------------------------------------------
-- تشخیص هوشمند layout (نسخهٔ قوی و مقاوم)
--------------------------------------------------------------------------------
local function detectLayout(typeValue)
if not typeValue or typeValue == "" then
return "school"
end
-- نرمالسازی قوی
local t = mw.ustring.lower(typeValue)
t = t:gsub("", "") -- حذف نیمفاصله
t = t:gsub("%s+", "") -- حذف همه فاصلهها
t = t:gsub("ي", "ی") -- یکسانسازی ی
t = t:gsub("ك", "ک") -- یکسانسازی ک
-- مطابقت دقیق بعد از نرمالسازی
local normalizedMap = {
["دانشگاه"] = "university",
["university"] = "university",
["حوزهعلمیه"] = "seminary",
["seminary"] = "seminary",
["آموزشگاه"] = "training_center",
["training_center"] = "training_center",
["آموزشگاههنری"] = "art_school",
["آموزشگاههنر"] = "art_school",
["هنری"] = "art_school",
["art_school"] = "art_school",
["آکادمیهنری"] = "art_school",
["آکادمیهنر"] = "art_school",
["پژوهشکده"] = "research_center",
["research_center"] = "research_center",
["کالج"] = "college",
["college"] = "college",
["مهدکودک"] = "kindergarten",
["kindergarten"] = "kindergarten",
["مدرسه"] = "school",
["school"] = "school",
}
if normalizedMap[t] then
return normalizedMap[t]
end
-- بررسی کلمات کلیدی
if t:find("هنری") or t:find("هنر") or t:find("آکادمی") then
return "art_school"
end
if t:find("دانشگاه") then
return "university"
end
if t:find("حوزه") or t:find("علمیه") then
return "seminary"
end
if t:find("پژوهش") then
return "research_center"
end
if t:find("کالج") then
return "college"
end
if t:find("مهد") or t:find("کودک") then
return "kindergarten"
end
if t:find("آموزشگاه") or t:find("مرکزآموزشی") or t:find("آموزشی") then
return "training_center"
end
if t:find("مدرسه") then
return "school"
end
return "school"
end
--------------------------------------------------------------------------------
-- RESOLVE INHERIT
--------------------------------------------------------------------------------
local function resolveLayout(layoutName)
local seen = {}
local current = layoutName
while current do
if seen[current] then
return layouts.school or {}
end
seen[current] = true
local layout = layouts[current]
if not layout then
return layouts.school or {}
end
if layout.inherit then
current = layout.inherit
else
return layout
end
end
return layouts.school or {}
end
--------------------------------------------------------------------------------
-- RENDER LAYOUT
--------------------------------------------------------------------------------
local function renderLayout(layoutName, args)
local layout = resolveLayout(layoutName)
local box = mw.html.create("table")
:addClass(INFOBOX_CLASS)
:addClass("education-" .. layoutName)
local rendered = {}
renderTitle(box, args)
rendered.logo = true
renderImage(box, args)
rendered.image = true
rendered.caption = true
for _, section in ipairs(layout) do
renderGroup(box, section.group, section.fields, args, rendered)
end
return tostring(box)
end
--------------------------------------------------------------------------------
-- MAIN
--------------------------------------------------------------------------------
function p.main(frame)
local args = normalizeArgs(getArgs(frame))
local layout = detectLayout(args.type)
return renderLayout(layout, args)
end
return p