Module:String utilities
Appearance
Documentation for this module may be created at Module:String utilities/doc
--[[
Module:String utilities
Part of the Module:Utilities family.
Covers pure string and table-of-strings operations — no Cargo, no wiki output.
All functions are re-exported through Module:Utilities for backward compatibility.
Functions:
sc Safe concatenation (nil-safe)
format_delimited_string Normalise spacing around a delimiter
parse_csv_to_table CSV string → trimmed array
parse_string_to_table Delimited string → formatted string (legacy; may be overly complex)
list_to_csv_string Array → comma-separated string
count_comma_elements Count items in a CSV string
count_double_tilde_elements Count items in a ~~-delimited string
in_table Check if a string exists in a table (array or dict mode)
element_sandwicher Wrap each array element in start/end characters
italicizer Apply <i> tags inside or around array elements
species_italicizer italicizer wrapper — italicise full string when no parens
parentheses_italicizer italicizer wrapper — italicise only inside parens
italicize_parenthetical Single-string wrapper around parentheses_italicizer, #invoke-able
word_italicizer Italicise specific words within a string
extract_parenthetical Extract text inside the first set of parentheses
strip_parenthetical Remove parenthetical suffix from a string
title_to_sci Extract scientific name from a wiki page title
remove_file_prefix Strip leading "File:" from a filename
add_file_prefix_gentle Add "File:" prefix safely (handles existing prefixes and links)
quote_sql_value Single-quote and escape a value for SQL use
escape_string_for_holds Escape a string for use in a Cargo HOLDS clause
escape_string_for_sql Escape a string for use in a standard Cargo SQL clause
--]]
local str = {}
-- ============================================================
-- Safe concatenation
-- ============================================================
-- Returns "" for nil values instead of raising an error.
function str.sc(...)
local parts = { ... }
for i = 1, #parts do
if parts[i] == nil then
parts[i] = ""
else
parts[i] = tostring(parts[i])
end
end
return table.concat(parts)
end
-- ============================================================
-- Delimited string helpers
-- ============================================================
--[[
Ensures there is exactly one space after each occurrence of the delimiter.
str.format_delimited_string("apple,banana,carrot") → "apple, banana, carrot"
str.format_delimited_string("apple;banana", ";") → "apple; banana"
--]]
function str.format_delimited_string(input_string, delimiter)
delimiter = delimiter or ","
return input_string:gsub(delimiter .. "%s*", delimiter .. " ")
end
--[[
Splits a comma-delimited string into a trimmed array.
Returns {} for nil input.
str.parse_csv_to_table(" value1 , value2 ,value3 ") → {"value1", "value2", "value3"}
--]]
function str.parse_csv_to_table(csv_string)
if csv_string == nil then return {} end
if type(csv_string) ~= "string" then
error("Invalid argument: csv_string must be a string")
end
local t = {}
for field in csv_string:gmatch("[^,]+") do
table.insert(t, mw.text.trim(field))
end
return t
end
--[[
Parses a comma-delimited string into a single formatted string.
Supports "page" (wiki links) and "ext_link" (external links) formats.
Carried forward as-is; may be a candidate for future simplification.
--]]
function str.parse_string_to_table(text, format, link_text)
if text then
local text_table = {}
for item in text:gmatch("[^,]+") do
if format == "page" then
item = "[[" .. item .. "]]"
elseif format == "ext_link" then
item = "[" .. item .. " " .. link_text .. "]"
else
item = item:gsub(",", ", ")
end
table.insert(text_table, item)
end
local full_text = table.concat(text_table, ", ")
return full_text
else
return full_text -- note: intentional carry-forward of original behaviour
end
end
-- Joins an array into a comma-separated string. Returns nil for empty input.
function str.list_to_csv_string(list)
if type(list) ~= "table" or #list == 0 then return nil end
return table.concat(list, ", ")
end
-- Counts elements in a CSV string. Returns 0 for nil or empty input.
function str.count_comma_elements(input_string)
if not input_string or input_string == "" then return 0 end
local count = 0
for _ in string.gmatch(input_string, "[^,]+") do
count = count + 1
end
return count == 0 and 1 or count
end
-- Counts non-blank elements in a ~~-delimited string.
function str.count_double_tilde_elements(input_string)
if not input_string or input_string == "" then return 0 end
local parts = mw.text.split(input_string, "~~", true)
local count = 0
for i = 1, #parts do
if mw.text.trim(parts[i]) ~= "" then count = count + 1 end
end
return count
end
-- ============================================================
-- Table searching
-- ============================================================
--[[
Checks whether a string exists in a table.
mode "array" (default) — search values
"dict" — search keys
partial true — allow substring matches
--]]
function str.in_table(tbl, s, mode, partial)
mode = mode or "array"
partial = partial or false
if mode == "dict" then
if partial then
for key, _ in pairs(tbl) do
if string.find(key, s) then return true end
end
return false
else
return tbl[s] ~= nil
end
else
for _, value in ipairs(tbl) do
if partial then
if string.find(value, s) then return true end
else
if value == s then return true end
end
end
return false
end
end
-- ============================================================
-- Array formatting
-- ============================================================
-- Wraps each element of an array with start_char and end_char.
function str.element_sandwicher(array, start_char, end_char)
local result = {}
for i = 1, #array do
if array[i] then
result[i] = start_char .. array[i] .. end_char
else
result[i] = start_char .. "No value" .. end_char
end
end
return result
end
--[[
Applies <i>...</i> formatting to elements of an array.
italicize_full_if_no_parens = true → italicise the whole string when no parens found
= false → italicise only text inside parentheses
--]]
function str.italicizer(array, italicize_full_if_no_parens)
local result = {}
for _, value in ipairs(array) do
local start_index, end_index = value:find("%b()")
local italicized_value
if start_index then
local inside = value:sub(start_index + 1, end_index - 1)
italicized_value =
value:sub(1, start_index - 1) ..
"(<i>" .. inside .. "</i>)" ..
value:sub(end_index + 1)
elseif italicize_full_if_no_parens then
italicized_value = "<i>" .. value .. "</i>"
else
italicized_value = value
end
table.insert(result, italicized_value)
end
return result
end
-- Italicise the full string when no parentheses are present (species page titles).
function str.species_italicizer(array)
return str.italicizer(array, true)
end
-- Italicise only text inside parentheses.
function str.parentheses_italicizer(array)
return str.italicizer(array, false)
end
--[[
Single-string wrapper around parentheses_italicizer, for use directly from
wikitext via #invoke (e.g. italicising the species binomial in a page title
like "Desert locust (Schistocerca gregaria)" wherever it appears as plain
text, such as a breadcrumb pill).
--]]
function str.italicize_parenthetical(frame)
local text = frame.args and frame.args[1] or frame
if not text or text == "" then return text end
return str.parentheses_italicizer({ text })[1]
end
--[[
Italicises specific words within a string using wiki ''markup''.
str.word_italicizer("Homo sapiens is cool", {"Homo sapiens"})
→ "''Homo sapiens'' is cool"
--]]
function str.word_italicizer(text, words_to_italicize)
for _, word in ipairs(words_to_italicize) do
text = text:gsub(
"(%f[%a]" .. word:gsub("(%W)", "%%%1") .. "%f[%A])",
"''%1''"
)
end
return text
end
-- ============================================================
-- Parenthetical extraction / stripping
-- ============================================================
-- Returns the text inside the first set of parentheses, or the full string.
local function extract_parenthetical(input_string)
return input_string:match("%((.-)%)") or input_string
end
str.extract_parenthetical = extract_parenthetical
-- Removes the parenthetical suffix and trims whitespace.
-- "Oaxaca (state in Mexico)" → "Oaxaca"
function str.strip_parenthetical(text)
return (text:gsub("%b()", ""):gsub("^%s*(.-)%s*$", "%1"))
end
-- Extracts the scientific name from a wiki page title of the form "Common Name (Genus species)".
function str.title_to_sci(s)
local start_idx, end_idx = string.find(s, "%b()")
if start_idx and end_idx then
return string.sub(s, start_idx + 1, end_idx - 1)
else
return s
end
end
-- ============================================================
-- File name helpers
-- ============================================================
-- Strips a leading "File:" prefix if present.
function str.remove_file_prefix(file_name)
local prefix = "File:"
if string.sub(file_name, 1, #prefix) == prefix then
return string.sub(file_name, #prefix + 1)
end
return file_name
end
--[[
Adds a "File:" prefix, handling existing prefixes and full File: links gracefully.
Returns "" for nil or blank input.
--]]
function str.add_file_prefix_gentle(file_name)
if not file_name or file_name == "" then return "" end
file_name = mw.text.trim(file_name)
local inner_file = mw.ustring.match(file_name, "^%[%[File:([^|%]]+)")
if inner_file then
return "File:" .. mw.text.trim(inner_file)
end
if not mw.ustring.match(file_name, "^[Ff]ile:") then
file_name = "File:" .. file_name
end
return file_name
end
-- ============================================================
-- SQL / Cargo escaping
-- ============================================================
--[[
Wraps a value in single quotes and escapes internal single quotes for SQL.
str.quote_sql_value("O'Brien") → "'O''Brien'"
--]]
function str.quote_sql_value(value)
value = mw.text.trim(value)
return "'" .. (value:gsub("'", "''")) .. "'"
end
-- Escapes a string for use in a Cargo HOLDS clause (single-quote escaping only).
function str.escape_string_for_holds(input_string)
if not input_string then return "" end
local sanitized = mw.text.trim(input_string)
sanitized = sanitized:gsub("'", "''")
return sanitized
end
-- Escapes a string for use in a standard Cargo SQL equality clause.
function str.escape_string_for_sql(input_string)
if not input_string then return "" end
local sanitized = mw.text.trim(input_string)
sanitized = sanitized:gsub("[#%%]", "")
sanitized = sanitized:gsub("'", "''")
return sanitized
end
return str