Module:ParseList: Difference between revisions

From bg3.wiki
Jump to navigation Jump to search
No edit summary
(Added no margin version of htmlListNoBullets)
 
(22 intermediate revisions by 3 users not shown)
Line 7: Line 7:
local makeElementsPlural
local makeElementsPlural
local makeElementsSingular
local makeElementsSingular
local finalJoiningWord
local makeElementsLowercase
local useTemplateOnElements
local useTwoArgTemplateOnElements
local textDelim
local textLastDelim


local function splitListString(listString)
local function splitListString(listString)
    local strings = {}
local strings = {}
    for str in listString:gmatch("[^" .. listDelimiters .. "]+") do
for str in listString:gmatch("[^" .. listDelimiters .. "]+") do
        table.insert(strings, str:match("^%s*(.-)%s*$"))
table.insert(strings, str:match("^%s*(.-)%s*$"))
    end
end
    return strings
return strings
end
end


-- We don't need to cover most regular English words, as they're very unlikely
-- This is a TWO-WAY conversion table of singular words and their corresponding
-- to ever be used with this module. The kind of stuff you want to add here is
-- plural form, for those that wouldn't be handled correctly by the automatic
-- gameplay elements like classes, weapons, races, etc. that you may want to
-- rules implemented further down below.  Note that we don't need to cover most
-- pluralize in a listing.
-- regular English words, as they're unlikely to ever be used with this module.
-- The kind of stuff you want to add here is gameplay elements like classes,
-- weapons, races, and so on.
local specialPlurals = {
local specialPlurals = {
-- Classes
Thief = "Thieves",
thief = "thieves",
-- Weapons
-- Weapons
Quarterstaff = "Quarterstaves",
Glaive = "Glaives",
quarterstaff = "quarterstaves",
glaive = "glaives",
Staff = "Staves",
staff = "staves",
-- Races
-- Races
Elf = "Elves",
elf = "elves",
["Half-Elf"] = "Half-Elves",
["half-elf"] = "half-elves",
Dwarf = "Dwarves",
dwarf = "dwarves",
Gith = "Gith",
Gith = "Gith",
gith = "gith",
gith = "gith",
Line 43: Line 42:
Drow = "Drow",
Drow = "Drow",
drow = "drow",
drow = "drow",
Dragonborn = "Dragonborn",
dragonborn = "dragonborn",
-- Creature types
-- Creature types
Fey = "Fey",
Fey = "Fey",
Line 50: Line 51:
}
}


local function pluralize(str)
-- Checks if str is equal to or ends in one of the keys in specialPlurals.
local sp = specialPlurals[str]
-- Returns the pluralized version if so, otherwise nil.
local function findSpecialPlural(str)
for singular, plural in pairs(specialPlurals) do
if str == singular then
return plural
end
local len = #singular
local suffix = str:sub(-len, -1)
if (suffix == singular) then
return str:sub(1, -len - 1) .. plural
end
end
end
 
-- Checks if str is equal to or ends in one of the values in specialPlurals.
-- Returns the singular version if so, otherwise nil.
local function findSpecialSingular(str)
for singular, plural in pairs(specialPlurals) do
if str == plural then
return singular
end
local len = #plural
local suffix = str:sub(-len, -1)
if suffix == plural then
return str:sub(1, -len - 1) .. singular
end
end
end
 
-- Checks for a special pluralization first, then implements these rules:
-- ...f -> ...ves
-- ...y -> ...ies
-- ...s -> ...ses
-- ...ch -> ...ches
-- ...sh -> ...shes
-- ... -> ...s
local function makePlural(str)
local sp = findSpecialPlural(str)
if sp then
if sp then
return sp
return sp
end
end
if str:sub(-1) == "y" then
local last1 = str:sub(-1)
if last1 == "f" then
return str:sub(1, -2) .. "ves"
elseif last1 == "y" then
return str:sub(1, -2) .. "ies"
return str:sub(1, -2) .. "ies"
elseif last1 == "s" then
return str .. "es"
end
local last2 = str:sub(-2)
if last2 == "ch" or last2 == "sh" then
return str .. "es"
end
end
return str .. "s"
return str .. "s"
end
end


local function processElement(str)
-- Checks for a special singularization first, then implements these rules:
-- ...ves -> ...f
-- ...ies -> ...y
-- ...ses -> ...s
-- ...ches -> ...ch
-- ...shes -> ...sh
-- ...s -> ...
-- ... -> ...
local function makeSingular(str)
local special = findSpecialSingular(str)
if special then
return special
end
local last3 = str:sub(-3)
if last3 == "ves" then
return str:sub(1, -4) .. "f"
elseif last3 == "ies" then
return str:sub(1, -4) .. "y"
elseif last3 == "ses" then
return str:sub(1, -3)
end
local last4 = str:sub(-4)
if last4 == "ches" or last4 == "shes" then
return str:sub(1, -3)
end
if str:sub(-1) == "s" then
return str:sub(1, -2)
end
return str
end
 
-- Applies the various per-element transforms.  Frame is needed for template
-- expansion; it may be nil if template transforms won't be applied.
local function processElement(str, frame)
local original = str
if makeElementsPlural then
if makeElementsPlural then
local plural = pluralize(str)
str = makePlural(str)
if makeElementsLinks then
elseif makeElementsSingular then
return "[[" .. str .. "|" .. plural .. "]]"
str = makeSingular(str)
else
end
return plural
if makeElementsLowercase then
end
str = str:lower()
else
end
if makeElementsLinks then
if makeElementsLinks then
return "[[" .. str .. "]]"
return "[[" .. original .. "|" .. str .. "]]"
else
elseif useTemplateOnElements then
return str
return frame:expandTemplate{
end
title = useTemplateOnElements,
args = { str }
}
elseif useTwoArgTemplateOnElements then
return frame:expandTemplate{
title = useTwoArgTemplateOnElements,
args = { original, str }
}
end
end
return str
end
end


-- These functions implement different output styles.  The elements will have
-- already gone through processElement() at this point, so they only need to be
-- glued together to produce the desired style of listing format.
local converters = {
local converters = {
sentence = function (elements)
text = function (elements)
    local count = #elements
local result = ""
    if count == 0 then
local count = #elements
    return ""
    elseif count == 1 then
    return elements[1]
    elseif count == 2 then
return elements[1] .. " " .. finalJoiningWord .. " " .. elements[2]
    end
    local result = ""
for i, str in ipairs(elements) do
for i, str in ipairs(elements) do
if i < count then
if i == 1 then
result = result .. str .. ", "
result = str
elseif i < count then
result = result .. textDelim .. str
else
else
result = result .. finalJoiningWord .. " " .. str
result = result .. textLastDelim .. str
end
end
    end
    return result
end,
htmlList = function (elements)
local result = ""
for i, str in ipairs(elements) do
result = result .. "* " .. str .. "\n"
end
end
return result
return result
Line 115: Line 195:
result = result .. ", " .. str
result = result .. ", " .. str
end
end
end
return result
end,
htmlList = function (elements)
local result = "<ul>\n"
for i, str in ipairs(elements) do
result = result .. "<li>" .. str .. "</li>\n"
end
return result .. "</ul>"
end,
htmlListNoBullets = function (elements)
local result = "<ul style='list-style: none;'>\n"
for i, str in ipairs(elements) do
result = result .. "<li>" .. str .. "</li>\n"
end
return result .. "</ul>"
end,
htmlListNoBulletsOrMargin = function (elements)
local result = "<ul style='list-style: none; margin: 0'>\n"
for i, str in ipairs(elements) do
result = result .. "<li>" .. str .. "</li>\n"
end
return result .. "</ul>"
end,
tableList = function (elements)
local result = "<div class=\"bg3wiki-tablelist\">"
for i, str in ipairs(elements) do
result = result .. str .. "\n"
end
return result .. "</div>"
end,
none = function (elements)
local result = ""
for i, str in ipairs(elements) do
result = result .. str
end
end
return result
return result
Line 122: Line 237:
function p.main(frame)
function p.main(frame)
local args = getArgs(frame, { frameOnly = true })
local args = getArgs(frame, { frameOnly = true })
return p._main(args)
return p._main(args, frame)
end
end


function p._main(args)
-- Frame is needed for template expansion; may be nil if the useTemplate and
-- useTemplate2 args are nil.
function p._main(args, frame)
listDelimiters = args['delimiter'] or ","
listDelimiters = args['delimiter'] or ","
makeElementsLinks = args['makeLinks']
makeElementsLinks = args['makeLinks']
makeElementsPlural = args['pluralize'] or args['makePlural']
makeElementsPlural = args['makePlural']
makeElementsSingular = args['makeSingular']
makeElementsSingular = args['makeSingular']
finalJoiningWord = args['joinWord'] or args['joiningWord'] or "and"
makeElementsLowercase = args['makeLowercase']
useTemplateOnElements = args['useTemplate']
useTwoArgTemplateOnElements = args['useTemplate2']
textDelim = args['textDelim']
if textDelim then
textLastDelim = args['textLastDelim'] or textDelim
else
textDelim = ', '
textLastDelim = args['textLastDelim'] or ', and '
end
local type = args['type'] or 'sentence'
local style = args['style'] or args['type'] or 'text'
local converter = converters[type]
local converter = converters[style]
local elements = {}
local elements = {}
local listString = args[1]
local listString = args[1]
local strings = splitListString(listString)
local strings = splitListString(listString)
for i, str in ipairs(strings) do
for i, str in ipairs(strings) do
table.insert(elements, processElement(str))
table.insert(elements, processElement(str, frame))
end
end
return converter(elements)
return converter(elements)

Latest revision as of 23:07, 11 October 2024

Doc page: Module:ParseList/doc

This module allows you to process a list of values (separated by a comma by default) and display them in a variety of useful formats.

{{#invoke:ParseList|main|Fighter, Cleric, Wizard}}

Becomes:

Fighter, Cleric, and Wizard

Parameters

The first parameter is the list to be processed. The remaining parameters are optional:

parameter default meaning
style 'text' The desired output format, see below for possibilities.
type Deprecated synonym for style.
delimiter ',' The character that should serve as a delimiter of the input list. This can actually be a string of multiple characters which will all work as a delimiter, but that usage is discouraged as it may cause confusion.
makePlural (empty) If provided and not blank, means that each element will be turned into a plural word.
makeSingular (empty) If provided and not blank, means that each element will be turned into singular.
makeLowercase (empty) If provided and not blank, means that each element will be made all-lowercase.
makeLinks (empty)

If provided and not blank, means that each element will be made into a link.

If any transforms like plural, singular, or lowercase were specified, the original element will be the link destination, and the transformed version the link text. E.g., the list element "Apple" would become [[Apple|apples]] if makePlural, makeLowercase, and makeLinks were provided.

useTemplate (empty)

If provided, will use the provided template on each element, i.e., turn every X into {{Template|X}}.

This is done after any transforms like plural, singular and lowercase. E.g., the list element "Apple" would become {{Template|apples}} if makePlural, makeLowercase, and useTemplate were provided.

useTemplate2 (empty)

If provided, will use the provided template on each element and its transformed form as two template arguments.

E.g., the list element "Apple" would become {{Template|Apple|apples}} if makePlural, makeLowercase, and useTemplate were provided.

textDelim ', '

If style is text, this is used as the delimiter for the output list.

Setting this causes the default value for textLastDelim to be changed to the same value, since the default value of textLastDelim is unlikely to be useful with anything other than the default value of textDelim.

Note that the default value is not just a comma; it's a comma followed by a space character.

textLastDelim (conditional)

If style is text, this is used as the delimiter between the last two elements of the output list.

If textDelim is not set (i.e., left at its default value), then the default value for this is ', and ' such that an input list "X, Y, Z" would be turned into "X, Y, and Z". You could leave textDelim unset, but set textLastDelim = <nowiki>, or </nowiki> to get the output "X, Y, or Z" instead.

If textDelim is set, then this defaults to the same value as that. E.g., specifying textDelim = <nowiki> + </nowiki> would transform the input list "X, Y, Z" into "X + Y + Z" without needing to also change the value of textLastDelim.

Note: The use of <nowiki> here is to prevent leading and trailing space characters from being ignored when setting textDelim or textLastDelim to a string that should start and/or end with a space character.

The automatic plural/singular transforms are able to recognize certain irregular plurals and handle them correctly, such as "thieves" and "quarterstaves." This is handled partly through a table of explicit special conversions like staff -> staves, and partly through logical rules like ...f --> ...ves. If you find a word that produces a wrong result, it should be added to the table of explicit conversions.

The makeLink, useTemplate, and useTemplate2 arguments are mutually exclusive; when several are provided, only the first will take effect.

Output styles

The value of the style parameter can be the following:

value example output
text Fighter, Cleric, and Wizard
simpleList Fighter, Cleric, Wizard
htmlList
  • Fighter
  • Cleric
  • Wizard
htmlListNoBullets
  • Fighter
  • Cleric
  • Wizard
htmlListNoBulletsOrMargin
  • Fighter
  • Cleric
  • Wizard
tableList
Fighter

Cleric Wizard

none FighterClericWizard

The style none is useful if each element is being transformed into an HTML element such that no textual delimiters are desired.

Examples

Make Links

{{#invoke: ParseList | main | Fighter, Cleric, Wizard | makeLinks = yes }}

Result:

Fighter, Cleric, and Wizard

Make plural

{{#invoke: ParseList | main | Fighter, Cleric, Wizard | makePlural = yes }}

Result:

Fighters, Clerics, and Wizards

Make Plural Links

{{#invoke: ParseList | main | Fighter, Cleric, Wizard | makeLinks = yes | makePlural = yes }}

Result:

Fighters, Clerics, and Wizards

Make Plural Links, end with "or"

{{#invoke: ParseList | main | Fighter, Cleric, Wizard | makeLinks = yes | makePlural = yes | textLastDelim = <nowiki>, or </nowiki> }}

Result:

Fighters, Clerics, or Wizards

Make Plural Links, turn into HTML list

{{#invoke: ParseList | main | Fighter, Cleric, Wizard | makeLinks = yes | makePlural = yes | style = htmlList }}

Result:


local getArgs = require('Module:Arguments').getArgs
local p = {}

-- Config options, set in main function
local listDelimiters
local makeElementsLinks
local makeElementsPlural
local makeElementsSingular
local makeElementsLowercase
local useTemplateOnElements
local useTwoArgTemplateOnElements
local textDelim
local textLastDelim

local function splitListString(listString)
	local strings = {}
	for str in listString:gmatch("[^" .. listDelimiters .. "]+") do
		table.insert(strings, str:match("^%s*(.-)%s*$"))
	end
	return strings
end

-- This is a TWO-WAY conversion table of singular words and their corresponding
-- plural form, for those that wouldn't be handled correctly by the automatic
-- rules implemented further down below.  Note that we don't need to cover most
-- regular English words, as they're unlikely to ever be used with this module.
-- The kind of stuff you want to add here is gameplay elements like classes,
-- weapons, races, and so on.
local specialPlurals = {
	-- Weapons
	Glaive = "Glaives",
	glaive = "glaives",
	Staff = "Staves",
	staff = "staves",
	-- Races
	Gith = "Gith",
	gith = "gith",
	Githyanki = "Githyanki",
	githyanki = "githyanki",
	Duergar = "Duergar",
	duergar = "duergar",
	Drow = "Drow",
	drow = "drow",
	Dragonborn = "Dragonborn",
	dragonborn = "dragonborn",
	-- Creature types
	Fey = "Fey",
	fey = "fey",
	Undead = "Undead",
	undead = "undead",
}

-- Checks if str is equal to or ends in one of the keys in specialPlurals.
-- Returns the pluralized version if so, otherwise nil.
local function findSpecialPlural(str)
	for singular, plural in pairs(specialPlurals) do
		if str == singular then
			return plural
		end
		local len = #singular
		local suffix = str:sub(-len, -1)
		if (suffix == singular) then
			return str:sub(1, -len - 1) .. plural
		end
	end
end

-- Checks if str is equal to or ends in one of the values in specialPlurals.
-- Returns the singular version if so, otherwise nil.
local function findSpecialSingular(str)
	for singular, plural in pairs(specialPlurals) do
		if str == plural then
			return singular
		end
		local len = #plural
		local suffix = str:sub(-len, -1)
		if suffix == plural then
			return str:sub(1, -len - 1) .. singular
		end
	end
end

-- Checks for a special pluralization first, then implements these rules:
-- ...f -> ...ves
-- ...y -> ...ies
-- ...s -> ...ses
-- ...ch -> ...ches
-- ...sh -> ...shes
-- ... -> ...s
local function makePlural(str)
	local sp = findSpecialPlural(str)
	if sp then
		return sp
	end
	local last1 = str:sub(-1)
	if last1 == "f" then
		return str:sub(1, -2) .. "ves"
	elseif last1 == "y" then
		return str:sub(1, -2) .. "ies"
	elseif last1 == "s" then
		return str .. "es"
	end
	local last2 = str:sub(-2)
	if last2 == "ch" or last2 == "sh" then
		return str .. "es"
	end
	return str .. "s"
end

-- Checks for a special singularization first, then implements these rules:
-- ...ves -> ...f
-- ...ies -> ...y
-- ...ses -> ...s
-- ...ches -> ...ch
-- ...shes -> ...sh
-- ...s -> ...
-- ... -> ...
local function makeSingular(str)
	local special = findSpecialSingular(str)
	if special then
		return special
	end
	local last3 = str:sub(-3)
	if last3 == "ves" then
		return str:sub(1, -4) .. "f"
	elseif last3 == "ies" then
		return str:sub(1, -4) .. "y"
	elseif last3 == "ses" then
		return str:sub(1, -3)
	end
	local last4 = str:sub(-4)
	if last4 == "ches" or last4 == "shes" then
		return str:sub(1, -3)
	end
	if str:sub(-1) == "s" then
		return str:sub(1, -2)
	end
	return str
end

-- Applies the various per-element transforms.  Frame is needed for template
-- expansion; it may be nil if template transforms won't be applied.
local function processElement(str, frame)
	local original = str
	if makeElementsPlural then
		str = makePlural(str)
	elseif makeElementsSingular then
		str = makeSingular(str)
	end
	if makeElementsLowercase then
		str = str:lower()
	end
	if makeElementsLinks then
		return "[[" .. original .. "|" .. str .. "]]"
	elseif useTemplateOnElements then
		return frame:expandTemplate{
			title = useTemplateOnElements,
			args = { str }
		}
	elseif useTwoArgTemplateOnElements then
		return frame:expandTemplate{
			title = useTwoArgTemplateOnElements,
			args = { original, str }
		}
	end
	return str
end

-- These functions implement different output styles.  The elements will have
-- already gone through processElement() at this point, so they only need to be
-- glued together to produce the desired style of listing format.
local converters = {
	text = function (elements)
		local result = ""
		local count = #elements
		for i, str in ipairs(elements) do
			if i == 1 then
				result = str
			elseif i < count then
				result = result .. textDelim .. str
			else
				result = result .. textLastDelim .. str
			end
		end
		return result
	end,
	simpleList = function (elements)
		local result = ""
		local first = true
		for i, str in ipairs(elements) do
			if first then
				result = str
				first = false
			else
				result = result .. ", " .. str
			end
		end
		return result
	end,
	htmlList = function (elements)
		local result = "<ul>\n"
		for i, str in ipairs(elements) do
			result = result .. "<li>" .. str .. "</li>\n"
		end
		return result .. "</ul>"
	end,
	htmlListNoBullets = function (elements)
		local result = "<ul style='list-style: none;'>\n"
		for i, str in ipairs(elements) do
			result = result .. "<li>" .. str .. "</li>\n"
		end
		return result .. "</ul>"
	end,
	htmlListNoBulletsOrMargin = function (elements)
		local result = "<ul style='list-style: none; margin: 0'>\n"
		for i, str in ipairs(elements) do
			result = result .. "<li>" .. str .. "</li>\n"
		end
		return result .. "</ul>"
	end,
	tableList = function (elements)
		local result = "<div class=\"bg3wiki-tablelist\">"
		for i, str in ipairs(elements) do
			result = result .. str .. "\n"
		end
		return result .. "</div>"
	end,
	none = function (elements)
		local result = ""
		for i, str in ipairs(elements) do
			result = result .. str
		end
		return result
	end,
}

function p.main(frame)
	local args = getArgs(frame, { frameOnly = true })
	return p._main(args, frame)
end

-- Frame is needed for template expansion; may be nil if the useTemplate and
-- useTemplate2 args are nil.
function p._main(args, frame)
	listDelimiters = args['delimiter'] or ","
	makeElementsLinks = args['makeLinks']
	makeElementsPlural = args['makePlural']
	makeElementsSingular = args['makeSingular']
	makeElementsLowercase = args['makeLowercase']
	useTemplateOnElements = args['useTemplate']
	useTwoArgTemplateOnElements = args['useTemplate2']
	textDelim = args['textDelim']
	if textDelim then
		textLastDelim = args['textLastDelim'] or textDelim
	else
		textDelim = ', '
		textLastDelim = args['textLastDelim'] or ', and '
	end
	
	local style = args['style'] or args['type'] or 'text'
	local converter = converters[style]
	local elements = {}
	local listString = args[1]
	local strings = splitListString(listString)
	for i, str in ipairs(strings) do
		table.insert(elements, processElement(str, frame))
	end
	return converter(elements)
end

return p