Module:Gallery: Difference between revisions
Appearance
Content deleted Content added
Reverting all the way back; alt text errors still present. Testing in sandbox will continue. |
Returning to the new version + fixes tested in the sandbox and in a local mediawiki. The problem was because the parser was interpreting the pipe as part of the url in the reported case. |
||
Line 1: | Line 1: | ||
-- This module implements {{gallery}} |
-- This module implements {{gallery}} by wrapping the <gallery> core extension tag. |
||
local p = {} |
local p = {} |
||
Line 5: | Line 5: | ||
local templatestyles = 'Module:Gallery/styles.css' |
local templatestyles = 'Module:Gallery/styles.css' |
||
local yesno = require('Module:Yesno') |
local yesno = require('Module:Yesno') |
||
local plaintextModule = require('Module:Plain text') |
|||
local function plaintext(text) |
|||
-- stips out external links without labels, |
|||
-- and then passes to the Plain_text module to clean the rest |
|||
return plaintextModule.main({ args = { |
|||
text:gsub("([^%[])%[([^%[%]%s]+)%]", '%1') |
|||
, encode = "no" } }) |
|||
end |
|||
local function trim(s) |
local function trim(s) |
||
return mw.ustring.gsub(mw.ustring.gsub(s, '%s', ' '), '^%s*(.-)%s*$', '%1') |
return mw.ustring.gsub(mw.ustring.gsub(s or '', '%s', ' '), '^%s*(.-)%s*$', '%1') |
||
end |
end |
||
local tracking, preview |
local tracking, preview |
||
local function isImage(file) |
|||
local file = trim(file):lower() -- Case insensitive check |
|||
-- Check if it starts with "File:", "Image:", or "Media:" |
|||
local prefix = file:match("^(%a+):") |
|||
if prefix and (prefix == "file" or prefix == "image" or prefix == "media") then |
|||
return true |
|||
end |
|||
local valid_extensions = { |
|||
"apng", "djvu", "flac", "gif", "jfi", "jfif", "jif", "jpe", "jpeg", "jpg", |
|||
"m1a", "m1v", "m2a", "m2v", "mid", "mp1", "mp2", "mp3", "mpa", "mpe", "mpeg", "mpg", |
|||
"mpv", "oga", "ogg", "ogv", "opus", "pdf", "png", "stl", "svg", "svgz", "tif", "tiff", |
|||
"wav", "wave", "webm", "webp", "xcf" |
|||
} |
|||
-- Extract file extension, of 3 or 4 characters only |
|||
local ext = file:match("%.(%w%w%w%w?)$") |
|||
-- Check if the extension is in the valid list |
|||
if ext then |
|||
for _, valid_ext in ipairs(valid_extensions) do |
|||
if ext == valid_ext then |
|||
table.insert(tracking, '[[Category:Pages using gallery without a media namespace prefix]]') |
|||
return true |
|||
end |
|||
end |
|||
end |
|||
return false |
|||
end |
|||
local function checkarg(k,v) |
local function checkarg(k,v) |
||
Line 17: | Line 57: | ||
k == 'width' or k == 'height' or k == 'whitebg' or |
k == 'width' or k == 'height' or k == 'whitebg' or |
||
k == 'mode' or k == 'footer' or k == 'perrow' or k == 'noborder' or |
k == 'mode' or k == 'footer' or k == 'perrow' or k == 'noborder' or |
||
k:match('^alt%d+$') or k:match('^%d+$') then |
k:match('^alt%d+$') or k:match('^class%d+$') or k:match('^%d+$') then |
||
-- valid |
-- valid |
||
elseif k == 'captionstyle' then |
elseif k == 'captionstyle' then |
||
Line 55: | Line 95: | ||
end |
end |
||
if (args.align or '') == 'centre' then |
|||
args.align = 'center' |
args.align = 'center' |
||
end |
end |
||
Line 97: | Line 137: | ||
end |
end |
||
local virtualgallery = {} |
|||
local gallery = {} |
local gallery = {} |
||
local imageCount = |
local imageCount = 0 |
||
local zwsp = string.char(0xE2, 0x80, 0x8B) -- U+200B Zero Width Space |
|||
for i = 1, imageCount do |
|||
local zwnj = string.char(0xE2, 0x80, 0x8C) -- U+200C Zero Width Non-Joiner |
|||
local img = trim(args[i*2 - 1] or '') |
|||
local caption = trim(args[i*2] or '') |
|||
-- create a coding to identify classes |
|||
local alt = trim(args['alt' .. i] or '') |
|||
-- using unicode non-printing characters |
|||
if img ~= '' then |
|||
-- this is a workaround until we get the class arg in the <gallery> tag |
|||
table.insert(gallery, img .. (alt ~= '' and ('|alt=' .. alt) or '') .. '|' .. caption ) |
|||
-- https://phabricator.wikimedia.org/T344784 |
|||
local skininvert = zwsp .. zwsp .. zwsp; |
|||
local bgtransparent = zwsp .. zwsp .. zwnj; |
|||
for i = 1, #args do |
|||
local currentfield = trim(args[i]) or '' |
|||
if currentfield == '' then |
|||
-- Skip empty fields |
|||
elseif isImage(currentfield) then |
|||
imageCount = imageCount + 1 |
|||
virtualgallery[imageCount] = { currentfield } |
|||
elseif imageCount > 0 and virtualgallery[imageCount][2] == nil then |
|||
-- In case of multiple captions, use the first and ignore the laters |
|||
virtualgallery[imageCount][2] = currentfield |
|||
end |
|||
end |
|||
local altCount = 0; |
|||
-- Run through virtualgallery and builds gallery |
|||
for n = 1, #virtualgallery do |
|||
local img = virtualgallery[n][1] |
|||
local caption = virtualgallery[n][2] or '' |
|||
local alt = trim(args['alt' .. n] or '') |
|||
local class = trim(args['class' .. n] or '') |
|||
-- we count alt text only before any modification |
|||
if alt ~= '' then |
|||
altCount = altCount + 1 |
|||
else |
|||
-- if alt is empty, we use the caption as a alt text. |
|||
-- It is necessary because we add classes codes in the next step, |
|||
-- we do not want to let the alt empty with just the non-printing characters. |
|||
alt = (caption ~= '') and plaintext(caption) or '' |
|||
end |
|||
-- we attach the non-printing code to the end of the alt text |
|||
if mw.ustring.find(class, 'skin%-invert') then -- this matches both skin-invert and skin-invert-image |
|||
alt = alt .. skininvert |
|||
end |
|||
-- as it is possible to combine multiple classes, we use find instead of == |
|||
if mw.ustring.find(class, 'bg%-transparent') then |
|||
alt = alt .. bgtransparent |
|||
end |
|||
-- Some space between the arguments and the pipe to prevent unexpected behaviors. |
|||
-- for example: in some cases the parser interpret the pipe as part of urls |
|||
table.insert(gallery, img .. |
|||
(alt ~= '' and (' |alt=' .. alt) or '') .. |
|||
(caption ~= '' and (' |' .. caption) or '')) |
|||
end |
|||
-- For tracking and verifying during migration to the new algorimth. |
|||
-- It can be removed once everything is verified |
|||
if math.ceil(#args / 2) > imageCount then |
|||
if altCount > 0 then |
|||
table.insert(tracking, '[[Category:Pages using gallery with potential alt text mismatch]]') |
|||
--else |
|||
-- table.insert(tracking, '[[Category:Pages using gallery with extra empty fields]]') |
|||
end |
end |
||
end |
end |
Latest revision as of 10:31, 10 February 2025
![]() | This module is rated as ready for general use. It has reached a mature form and is thought to be relatively bug-free and ready for use wherever appropriate. It is ready to mention on help pages and other Wikipedia resources as an option for new users to learn. To reduce server load and bad output, it should be improved by sandbox testing rather than repeated trial-and-error editing. |
![]() | This module is subject to page protection. It is a highly visible module in use by a very large number of pages, or is substituted very frequently. Because vandalism or mistakes would affect many pages, and even trivial editing might cause substantial load on the servers, it is protected from editing. |
![]() | This Lua module is used on approximately 15,000 pages and changes may be widely noticed. Test changes in the module's /sandbox or /testcases subpages, or in your own module sandbox. Consider discussing changes on the talk page before implementing them. |
![]() | This module depends on the following other modules: |
![]() | This module uses TemplateStyles: |
Implements {{gallery}}
Tracking categories
-- This module implements {{gallery}} by wrapping the <gallery> core extension tag.
local p = {}
local templatestyles = 'Module:Gallery/styles.css'
local yesno = require('Module:Yesno')
local plaintextModule = require('Module:Plain text')
local function plaintext(text)
-- stips out external links without labels,
-- and then passes to the Plain_text module to clean the rest
return plaintextModule.main({ args = {
text:gsub("([^%[])%[([^%[%]%s]+)%]", '%1')
, encode = "no" } })
end
local function trim(s)
return mw.ustring.gsub(mw.ustring.gsub(s or '', '%s', ' '), '^%s*(.-)%s*$', '%1')
end
local tracking, preview
local function isImage(file)
local file = trim(file):lower() -- Case insensitive check
-- Check if it starts with "File:", "Image:", or "Media:"
local prefix = file:match("^(%a+):")
if prefix and (prefix == "file" or prefix == "image" or prefix == "media") then
return true
end
local valid_extensions = {
"apng", "djvu", "flac", "gif", "jfi", "jfif", "jif", "jpe", "jpeg", "jpg",
"m1a", "m1v", "m2a", "m2v", "mid", "mp1", "mp2", "mp3", "mpa", "mpe", "mpeg", "mpg",
"mpv", "oga", "ogg", "ogv", "opus", "pdf", "png", "stl", "svg", "svgz", "tif", "tiff",
"wav", "wave", "webm", "webp", "xcf"
}
-- Extract file extension, of 3 or 4 characters only
local ext = file:match("%.(%w%w%w%w?)$")
-- Check if the extension is in the valid list
if ext then
for _, valid_ext in ipairs(valid_extensions) do
if ext == valid_ext then
table.insert(tracking, '[[Category:Pages using gallery without a media namespace prefix]]')
return true
end
end
end
return false
end
local function checkarg(k,v)
if k and type(k) == 'string' then
if k == 'align' or k == 'state' or k == 'style' or k == 'title' or
k == 'width' or k == 'height' or k == 'whitebg' or
k == 'mode' or k == 'footer' or k == 'perrow' or k == 'noborder' or
k:match('^alt%d+$') or k:match('^class%d+$') or k:match('^%d+$') then
-- valid
elseif k == 'captionstyle' then
if not v:match('^text%-align%s*:%s*center[;%s]*$') then
table.insert(tracking, '[[Category:Pages using gallery with the captionstyle parameter]]')
end
else
-- invalid
local vlen = mw.ustring.len(k)
k = mw.ustring.sub(k, 1, (vlen < 25) and vlen or 25)
k = mw.ustring.gsub(k, '[^%w%-_ ]', '?')
table.insert(tracking, '[[Category:Pages using gallery with unknown parameters|' .. k .. ']]')
table.insert(preview, '"' .. k .. '"')
end
end
end
function p.gallery(frame)
-- If called via #invoke, use the args passed into the invoking template.
-- Otherwise, for testing purposes, assume args are being passed directly in.
local origArgs = (type(frame.getParent) == 'function') and frame:getParent().args or frame
-- ParserFunctions considers the empty string to be false, so to preserve the previous
-- behavior of {{gallery}}, change any empty arguments to nil, so Lua will consider
-- them false too.
local args = {}
tracking, preview = {}, {}
for k, v in pairs(origArgs) do
if v ~= '' then
args[k] = v
checkarg(k,v)
end
end
if (args.mode or '') == 'packed' and (args.align or '') == '' then
args.align = 'center'
end
if (args.align or '') == 'centre' then
args.align = 'center'
end
local tbl = mw.html.create('div')
tbl:addClass('mod-gallery')
if args.state then
tbl
:addClass('mod-gallery-collapsible')
:addClass('collapsible')
:addClass(args.state)
end
if args.style then
tbl:cssText(args.style)
else
tbl:addClass('mod-gallery-default')
end
if args.align then
tbl:addClass('mod-gallery-' .. args.align:lower())
end
if args.title then
tbl:tag('div')
:addClass('title')
:tag('div')
:wikitext(args.title)
end
local gargs = {}
gargs['class'] = 'nochecker' .. (args.noborder and '' or ' bordered-images')
gargs['widths'] = tonumber(args.width) or 180
gargs['heights'] = tonumber(args.height) or 180
gargs['style'] = args.captionstyle
gargs['perrow'] = args.perrow
gargs['mode'] = args.mode
if yesno(args.whitebg or 'yes') then
gargs['class'] = gargs['class'] .. ' whitebg'
end
local virtualgallery = {}
local gallery = {}
local imageCount = 0
local zwsp = string.char(0xE2, 0x80, 0x8B) -- U+200B Zero Width Space
local zwnj = string.char(0xE2, 0x80, 0x8C) -- U+200C Zero Width Non-Joiner
-- create a coding to identify classes
-- using unicode non-printing characters
-- this is a workaround until we get the class arg in the <gallery> tag
-- https://phabricator.wikimedia.org/T344784
local skininvert = zwsp .. zwsp .. zwsp;
local bgtransparent = zwsp .. zwsp .. zwnj;
for i = 1, #args do
local currentfield = trim(args[i]) or ''
if currentfield == '' then
-- Skip empty fields
elseif isImage(currentfield) then
imageCount = imageCount + 1
virtualgallery[imageCount] = { currentfield }
elseif imageCount > 0 and virtualgallery[imageCount][2] == nil then
-- In case of multiple captions, use the first and ignore the laters
virtualgallery[imageCount][2] = currentfield
end
end
local altCount = 0;
-- Run through virtualgallery and builds gallery
for n = 1, #virtualgallery do
local img = virtualgallery[n][1]
local caption = virtualgallery[n][2] or ''
local alt = trim(args['alt' .. n] or '')
local class = trim(args['class' .. n] or '')
-- we count alt text only before any modification
if alt ~= '' then
altCount = altCount + 1
else
-- if alt is empty, we use the caption as a alt text.
-- It is necessary because we add classes codes in the next step,
-- we do not want to let the alt empty with just the non-printing characters.
alt = (caption ~= '') and plaintext(caption) or ''
end
-- we attach the non-printing code to the end of the alt text
if mw.ustring.find(class, 'skin%-invert') then -- this matches both skin-invert and skin-invert-image
alt = alt .. skininvert
end
-- as it is possible to combine multiple classes, we use find instead of ==
if mw.ustring.find(class, 'bg%-transparent') then
alt = alt .. bgtransparent
end
-- Some space between the arguments and the pipe to prevent unexpected behaviors.
-- for example: in some cases the parser interpret the pipe as part of urls
table.insert(gallery, img ..
(alt ~= '' and (' |alt=' .. alt) or '') ..
(caption ~= '' and (' |' .. caption) or ''))
end
-- For tracking and verifying during migration to the new algorimth.
-- It can be removed once everything is verified
if math.ceil(#args / 2) > imageCount then
if altCount > 0 then
table.insert(tracking, '[[Category:Pages using gallery with potential alt text mismatch]]')
--else
-- table.insert(tracking, '[[Category:Pages using gallery with extra empty fields]]')
end
end
tbl:tag('div')
:addClass('main')
:tag('div')
:wikitext(
frame:extensionTag{ name = 'gallery', content = '\n' .. table.concat(gallery,'\n'), args = gargs}
)
if args.footer then
tbl:tag('div')
:addClass('footer')
:tag('div')
:wikitext(args.footer)
end
local trackstr = (#tracking > 0) and table.concat(tracking, '') or ''
if #preview > 0 then
trackstr = require('Module:If preview')._warning({
'Unknown parameters ' .. table.concat(preview, '; ') .. '.'
}) .. trackstr
end
return frame:extensionTag{ name = 'templatestyles', args = { src = templatestyles} } .. tostring(tbl) .. trackstr
end
return p