Theme: iWiki Log in Register

Diff: Module:String2

Comparing revision #1 (2022-05-14 12:03:50) with revision #2 (2023-02-04 10:26:34).

OldNew
local p = {}
local p = {}
p.trim = function(frame)
p.trim = function(frame)
	return mw.text.trim(frame.args[1] or "")
	return mw.text.trim(frame.args[1] or "")
end
end
p.sentence = function (frame)
p.sentence = function (frame)
	-- {{lc:}} is strip-marker safe, string.lower is not.
	-- {{lc:}} is strip-marker safe, string.lower is not.
	frame.args[1] = frame:callParserFunction('lc', frame.args[1])
	frame.args[1] = frame:callParserFunction('lc', frame.args[1])
	return p.ucfirst(frame)
	return p.ucfirst(frame)
end
end
p.ucfirst = function (frame )
p.ucfirst = function (frame )
	local s =  mw.text.trim( frame.args[1] or "" )
	local s =  mw.text.trim( frame.args[1] or "" )
	local s1 = ""
	local s1 = ""
	-- if it's a list chop off and (store as s1) everything up to the first <li>
	-- if it's a list chop off and (store as s1) everything up to the first <li>
	local lipos = mw.ustring.find(s, "<li>" )
	local lipos = mw.ustring.find(s, "<li>" )
	if lipos then
	if lipos then
		s1 = mw.ustring.sub(s, 1, lipos + 3)
		s1 = mw.ustring.sub(s, 1, lipos + 3)
		s = mw.ustring.sub(s, lipos + 4)
		s = mw.ustring.sub(s, lipos + 4)
	end
	end
	-- s1 is either "" or the first part of the list markup, so we can continue
	-- s1 is either "" or the first part of the list markup, so we can continue
	-- and prepend s1 to the returned string
	-- and prepend s1 to the returned string
	local letterpos
	local letterpos
	if mw.ustring.find(s, "^%[%[[^|]+|[^%]]+%]%]") then
	if mw.ustring.find(s, "^%[%[[^|]+|[^%]]+%]%]") then
		-- this is a piped wikilink, so we capitalise the text, not the pipe
		-- this is a piped wikilink, so we capitalise the text, not the pipe
		local _
		local _
		_, letterpos = mw.ustring.find(s, "|%W*%w") -- find the first letter after the pipe
		_, letterpos = mw.ustring.find(s, "|%W*%w") -- find the first letter after the pipe
	else
	else
		letterpos = mw.ustring.find(s, '%w')
		letterpos = mw.ustring.find(s, '%w')
	end
	end
	if letterpos then
	if letterpos then
		local first = mw.ustring.sub(s, 1, letterpos - 1)
		local first = mw.ustring.sub(s, 1, letterpos - 1)
		local letter = mw.ustring.sub(s, letterpos, letterpos)
		local letter = mw.ustring.sub(s, letterpos, letterpos)
		local rest = mw.ustring.sub(s, letterpos + 1)
		local rest = mw.ustring.sub(s, letterpos + 1)
		return s1 .. first .. mw.ustring.upper(letter) .. rest
		return s1 .. first .. mw.ustring.upper(letter) .. rest
	else
	else
		return s1 .. s
		return s1 .. s
	end
	end
end
end
p.title = function (frame )
p.title = function (frame )
	-- http://grammar.yourdictionary.com/capitalization/rules-for-capitalization-in-titles.html
	-- http://grammar.yourdictionary.com/capitalization/rules-for-capitalization-in-titles.html
	-- recommended by The U.S. Government Printing Office Style Manual:
	-- recommended by The U.S. Government Printing Office Style Manual:
	-- "Capitalize all words in titles of publications and documents,
	-- "Capitalize all words in titles of publications and documents,
	-- except a, an, the, at, by, for, in, of, on, to, up, and, as, but, or, and nor."
	-- except a, an, the, at, by, for, in, of, on, to, up, and, as, but, or, and nor."
	local alwayslower = {['a'] = 1, ['an'] = 1, ['the'] = 1,
	local alwayslower = {['a'] = 1, ['an'] = 1, ['the'] = 1,
		['and'] = 1, ['but'] = 1, ['or'] = 1, ['for'] = 1,
		['and'] = 1, ['but'] = 1, ['or'] = 1, ['for'] = 1,
		['nor'] = 1, ['on'] = 1, ['in'] = 1, ['at'] = 1, ['to'] = 1,
		['nor'] = 1, ['on'] = 1, ['in'] = 1, ['at'] = 1, ['to'] = 1,
		['from'] = 1, ['by'] = 1, ['of'] = 1, ['up'] = 1 }
		['from'] = 1, ['by'] = 1, ['of'] = 1, ['up'] = 1 }
	local res = ''
	local res = ''
	local s =  mw.text.trim( frame.args[1] or "" )
	local s =  mw.text.trim( frame.args[1] or "" )
	local words = mw.text.split( s, " ")
	local words = mw.text.split( s, " ")
	for i, s in ipairs(words) do
	for i, s in ipairs(words) do
		-- {{lc:}} is strip-marker safe, string.lower is not.
		-- {{lc:}} is strip-marker safe, string.lower is not.
		s = frame:callParserFunction('lc', s)
		s = frame:callParserFunction('lc', s)
		if i == 1 or alwayslower[s] ~= 1 then
		if i == 1 or alwayslower[s] ~= 1 then
			s = mw.getContentLanguage():ucfirst(s)
			s = mw.getContentLanguage():ucfirst(s)
		end
		end
		words[i] = s
		words[i] = s
	end
	end
	return table.concat(words, " ")
	return table.concat(words, " ")
end
end
-- findlast finds the last item in a list
-- findlast finds the last item in a list
-- the first unnamed parameter is the list
-- the first unnamed parameter is the list
-- the second, optional unnamed parameter is the list separator (default = comma space)
-- the second, optional unnamed parameter is the list separator (default = comma space)
-- returns the whole list if separator not found
-- returns the whole list if separator not found
p.findlast = function(frame)
p.findlast = function(frame)
	local s =  mw.text.trim( frame.args[1] or "" )
	local s =  mw.text.trim( frame.args[1] or "" )
	local sep = frame.args[2] or ""
	local sep = frame.args[2] or ""
	if sep == "" then sep = ", " end
	if sep == "" then sep = ", " end
	local pattern = ".*" .. sep .. "(.*)"
	local pattern = ".*" .. sep .. "(.*)"
	local a, b, last = s:find(pattern)
	local a, b, last = s:find(pattern)
	if a then
	if a then
		return last
		return last
	else
	else
		return s
		return s
	end
	end
end
end
-- stripZeros finds the first number and strips leading zeros (apart from units)
-- stripZeros finds the first number and strips leading zeros (apart from units)
-- e.g "0940" -> "940"; "Year: 0023" -> "Year: 23"; "00.12" -> "0.12"
-- e.g "0940" -> "940"; "Year: 0023" -> "Year: 23"; "00.12" -> "0.12"
p.stripZeros = function(frame)
p.stripZeros = function(frame)
	local s = mw.text.trim(frame.args[1] or "")
	local s = mw.text.trim(frame.args[1] or "")
	local n = tonumber( string.match( s, "%d+" ) ) or ""
	local n = tonumber( string.match( s, "%d+" ) ) or ""
	s = string.gsub( s, "%d+", n, 1 )
	s = string.gsub( s, "%d+", n, 1 )
	return s
	return s
end
end
-- nowiki ensures that a string of text is treated by the MediaWiki software as just a string
-- nowiki ensures that a string of text is treated by the MediaWiki software as just a string
-- it takes an unnamed parameter and trims whitespace, then removes any wikicode
-- it takes an unnamed parameter and trims whitespace, then removes any wikicode
p.nowiki = function(frame)
p.nowiki = function(frame)
	local str = mw.text.trim(frame.args[1] or "")
	local str = mw.text.trim(frame.args[1] or "")
	return mw.text.nowiki(str)
	return mw.text.nowiki(str)
end
end
-- split splits text at boundaries specified by separator
-- split splits text at boundaries specified by separator
-- and returns the chunk for the index idx (starting at 1)
-- and returns the chunk for the index idx (starting at 1)
-- #invoke:String2 |split |text |separator |index |true/false
-- #invoke:String2 |split |text |separator |index |true/false
-- #invoke:String2 |split |txt=text |sep=separator |idx=index |plain=true/false
-- #invoke:String2 |split |txt=text |sep=separator |idx=index |plain=true/false
-- if plain is false/no/0 then separator is treated as a Lua pattern - defaults to plain=true
-- if plain is false/no/0 then separator is treated as a Lua pattern - defaults to plain=true
p.split = function(frame)
p.split = function(frame)
	local args = frame.args
	local args = frame.args
	if not(args[1] or args.txt) then args = frame:getParent().args end
	if not(args[1] or args.txt) then args = frame:getParent().args end
	local txt = args[1] or args.txt or ""
	local txt = args[1] or args.txt or ""
	if txt == "" then return nil end
	if txt == "" then return nil end
	local sep = (args[2] or args.sep or ""):gsub('"', '')
	local sep = (args[2] or args.sep or ""):gsub('"', '')
	local idx = tonumber(args[3] or args.idx) or 1
	local idx = tonumber(args[3] or args.idx) or 1
	local plain = (args[4] or args.plain or "true"):sub(1,1)
	local plain = (args[4] or args.plain or "true"):sub(1,1)
	plain = (plain ~= "f" and plain ~= "n" and plain ~= "0")
	plain = (plain ~= "f" and plain ~= "n" and plain ~= "0")
	local splittbl = mw.text.split( txt, sep, plain )
	local splittbl = mw.text.split( txt, sep, plain )
	if idx < 0 then idx = #splittbl + idx + 1 end
	if idx < 0 then idx = #splittbl + idx + 1 end
	return splittbl[idx]
	return splittbl[idx]
end
end
-- val2percent scans through a string, passed as either the first unnamed parameter or |txt=
-- val2percent scans through a string, passed as either the first unnamed parameter or |txt=
-- it converts each number it finds into a percentage and returns the resultant string.
-- it converts each number it finds into a percentage and returns the resultant string.
p.val2percent = function(frame)
p.val2percent = function(frame)
	local args = frame.args
	local args = frame.args
	if not(args[1] or args.txt) then args = frame:getParent().args end
	if not(args[1] or args.txt) then args = frame:getParent().args end
	local txt = mw.text.trim(args[1] or args.txt or "")
	local txt = mw.text.trim(args[1] or args.txt or "")
	if txt == "" then return nil end
	if txt == "" then return nil end
	local function v2p (x)
	local function v2p (x)
		x = (tonumber(x) or 0) * 100
		x = (tonumber(x) or 0) * 100
		if x == math.floor(x) then x = math.floor(x) end
		if x == math.floor(x) then x = math.floor(x) end
		return x .. "%"
		return x .. "%"
	end
	end
	txt = txt:gsub("%d[%d%.]*", v2p) -- store just the string
	txt = txt:gsub("%d[%d%.]*", v2p) -- store just the string
	return txt
	return txt
end
end
-- one2a scans through a string, passed as either the first unnamed parameter or |txt=
-- one2a scans through a string, passed as either the first unnamed parameter or |txt=
-- it converts each occurrence of 'one ' into either 'a ' or 'an ' and returns the resultant string.
-- it converts each occurrence of 'one ' into either 'a ' or 'an ' and returns the resultant string.
p.one2a = function(frame)
p.one2a = function(frame)
	local args = frame.args
	local args = frame.args
	if not(args[1] or args.txt) then args = frame:getParent().args end
	if not(args[1] or args.txt) then args = frame:getParent().args end
	local txt = mw.text.trim(args[1] or args.txt or "")
	local txt = mw.text.trim(args[1] or args.txt or "")
	if txt == "" then return nil end
	if txt == "" then return nil end
	txt = txt:gsub(" one ", " a "):gsub("^one", "a"):gsub("One ", "A "):gsub("a ([aeiou])", "an %1"):gsub("A ([aeiou])", "An %1")
	txt = txt:gsub(" one ", " a "):gsub("^one", "a"):gsub("One ", "A "):gsub("a ([aeiou])", "an %1"):gsub("A ([aeiou])", "An %1")
	return txt
	return txt
end
end
-- findpagetext returns the position of a piece of text in a page
-- findpagetext returns the position of a piece of text in a page
-- First positional parameter or |text is the search text
-- First positional parameter or |text is the search text
-- Optional parameter |title is the page title, defaults to current page
-- Optional parameter |title is the page title, defaults to current page
-- Optional parameter |plain is either true for plain search (default) or false for Lua pattern search
-- Optional parameter |plain is either true for plain search (default) or false for Lua pattern search
-- Optional parameter |nomatch is the return value when no match is found; default is nil
-- Optional parameter |nomatch is the return value when no match is found; default is nil
p._findpagetext = function(args)
p._findpagetext = function(args)
	-- process parameters
	-- process parameters
	local nomatch = args.nomatch or ""
	local nomatch = args.nomatch or ""
	if nomatch == "" then nomatch = nil end
	if nomatch == "" then nomatch = nil end
	--
	--
	local text = mw.text.trim(args[1] or args.text or "")
	local text = mw.text.trim(args[1] or args.text or "")
	if text == "" then return nil end
	if text == "" then return nil end
	--
	--
	local title = args.title or ""
	local title = args.title or ""
	local titleobj
	local titleobj
	if title == "" then
	if title == "" then
		titleobj = mw.title.getCurrentTitle()
		titleobj = mw.title.getCurrentTitle()
	else
	else
		titleobj = mw.title.new(title)
		titleobj = mw.title.new(title)
	end
	end
	--
	--
	local plain = args.plain or ""
	local plain = args.plain or ""
	if plain:sub(1, 1) == "f" then plain = false else plain = true end
	if plain:sub(1, 1) == "f" then plain = false else plain = true end
	-- get the page content and look for 'text' - return position or nomatch
	-- get the page content and look for 'text' - return position or nomatch
	local content = titleobj and titleobj:getContent()
	local content = titleobj and titleobj:getContent()
	return content and mw.ustring.find(content, text, 1, plain) or nomatch
	return content and mw.ustring.find(content, text, 1, plain) or nomatch
end
end
p.findpagetext = function(frame)
p.findpagetext = function(frame)
	local args = frame.args
	local args = frame.args
	local pargs = frame:getParent().args
	local pargs = frame:getParent().args
	for k, v in pairs(pargs) do
	for k, v in pairs(pargs) do
		args[k] = v
		args[k] = v
	end
	end
	if not (args[1] or args.text) then return nil end
	if not (args[1] or args.text) then return nil end
	-- just the first value
	-- just the first value
	return (p._findpagetext(args))
	return (p._findpagetext(args))
end
end
-- returns the decoded url. Inverse of parser function {{urlencode:val|TYPE}}
-- returns the decoded url. Inverse of parser function {{urlencode:val|TYPE}}
-- Type is:
-- Type is:
-- QUERY decodes + to space (default)
-- QUERY decodes + to space (default)
-- PATH does no extra decoding
-- PATH does no extra decoding
-- WIKI decodes _ to space
-- WIKI decodes _ to space
p._urldecode = function(url, type)
p._urldecode = function(url, type)
	url = url or ""
	url = url or ""
	type = (type == "PATH" or type == "WIKI") and type
	type = (type == "PATH" or type == "WIKI") and type
	return mw.uri.decode( url, type )
	return mw.uri.decode( url, type )
end
end
-- {{#invoke:String2|urldecode|url=url|type=type}}
-- {{#invoke:String2|urldecode|url=url|type=type}}
p.urldecode = function(frame)
p.urldecode = function(frame)
	return mw.uri.decode( frame.args.url, frame.args.type )
	return mw.uri.decode( frame.args.url, frame.args.type )
end
end
-- what follows was merged from Module:StringFunc
-- what follows was merged from Module:StringFunc
-- helper functions
-- helper functions
p._GetParameters = require('Module:GetParameters')
p._GetParameters = require('Module:GetParameters')
-- Argument list helper function, as per Module:String
-- Argument list helper function, as per Module:String
p._getParameters = p._GetParameters.getParameters
p._getParameters = p._GetParameters.getParameters
-- Escape Pattern helper function so that all characters are treated as plain text, as per Module:String
-- Escape Pattern helper function so that all characters are treated as plain text, as per Module:String
function p._escapePattern( pattern_str)
function p._escapePattern( pattern_str)
	return mw.ustring.gsub( pattern_str, "([%(%)%.%%%+%-%*%?%[%^%$%]])", "%%%1" )
	return mw.ustring.gsub( pattern_str, "([%(%)%.%%%+%-%*%?%[%^%$%]])", "%%%1" )
end
end
-- Helper Function to interpret boolean strings, as per Module:String
-- Helper Function to interpret boolean strings, as per Module:String
p._getBoolean = p._GetParameters.getBoolean
p._getBoolean = p._GetParameters.getBoolean
--[[
--[[
Strip
Strip
This function Strips characters from string
This function Strips characters from string
Usage:
Usage:
{{#invoke:String2|strip|source_string|characters_to_strip|plain_flag}}
{{#invoke:String2|strip|source_string|characters_to_strip|plain_flag}}
Parameters
Parameters
	source: The string to strip
	source: The string to strip
	chars:  The pattern or list of characters to strip from string, replaced with ''
	chars:  The pattern or list of characters to strip from string, replaced with ''
	plain:  A flag indicating that the chars should be understood as plain text. defaults to true.
	plain:  A flag indicating that the chars should be understood as plain text. defaults to true.
Leading and trailing whitespace is also automatically stripped from the string.
Leading and trailing whitespace is also automatically stripped from the string.
]]
]]
function p.strip( frame )
function p.strip( frame )
	local new_args = p._getParameters( frame.args,  {'source', 'chars', 'plain'} )
	local new_args = p._getParameters( frame.args,  {'source', 'chars', 'plain'} )
	local source_str = new_args['source'] or ''
	local source_str = new_args['source'] or ''
	local chars = new_args['chars'] or '' or 'characters'
	local chars = new_args['chars'] or '' or 'characters'
	source_str = mw.text.trim(source_str)
	source_str = mw.text.trim(source_str)
	if source_str == '' or chars == '' then
	if source_str == '' or chars == '' then
		return source_str
		return source_str
	end
	end
	local l_plain = p._getBoolean( new_args['plain'] or true )
	local l_plain = p._getBoolean( new_args['plain'] or true )
	if l_plain then
	if l_plain then
		chars = p._escapePattern( chars )
		chars = p._escapePattern( chars )
	end
	end
	local result
	local result
	result = mw.ustring.gsub(source_str, "["..chars.."]", '')
	result = mw.ustring.gsub(source_str, "["..chars.."]", '')
	return result
	return result
end
end
--[[
--[[
Match any
Match any
Returns the index of the first given pattern to match the input. Patterns must be consecutively numbered.
Returns the index of the first given pattern to match the input. Patterns must be consecutively numbered.
Returns the empty string if nothing matches for use in {{#if:}}
Returns the empty string if nothing matches for use in {{#if:}}
Usage:
Usage:
	{{#invoke:String2|matchAll|source=123 abc|456|abc}} returns '2'.
	{{#invoke:String2|matchAll|source=123 abc|456|abc}} returns '2'.
Parameters:
Parameters:
	source: the string to search
	source: the string to search
	plain:  A flag indicating that the patterns should be understood as plain text. defaults to true.
	plain:  A flag indicating that the patterns should be understood as plain text. defaults to true.
	1, 2, 3, ...: the patterns to search for
	1, 2, 3, ...: the patterns to search for
]]
]]
function p.matchAny(frame)
function p.matchAny(frame)
	local source_str = frame.args['source'] or error('The source parameter is mandatory.')
	local source_str = frame.args['source'] or error('The source parameter is mandatory.')
	local l_plain = p._getBoolean( frame.args['plain'] or true )
	local l_plain = p._getBoolean( frame.args['plain'] or true )
	for i = 1, math.huge do
	for i = 1, math.huge do
		local pattern = frame.args[i]
		local pattern = frame.args[i]
		if not pattern then return '' end
		if not pattern then return '' end
		if mw.ustring.find(source_str, pattern, 1, l_plain) then
		if mw.ustring.find(source_str, pattern, 1, l_plain) then
			return tostring(i)
			return tostring(i)
		end
		end
	end
	end
end
end
--[[--------------------------< H Y P H E N _ T O _ D A S H >--------------------------------------------------
--[[--------------------------< H Y P H E N _ T O _ D A S H >--------------------------------------------------
Converts a hyphen to a dash under certain conditions.  The hyphen must separate
Converts a hyphen to a dash under certain conditions.  The hyphen must separate
like items; unlike items are returned unmodified.  These forms are modified:
like items; unlike items are returned unmodified.  These forms are modified:
	letter - letter (A - B)
	letter - letter (A - B)
	digit - digit (4-5)
	digit - digit (4-5)
	digit separator digit - digit separator digit (4.1-4.5 or 4-1-4-5)
	digit separator digit - digit separator digit (4.1-4.5 or 4-1-4-5)
	letterdigit - letterdigit (A1-A5) (an optional separator between letter and
	letterdigit - letterdigit (A1-A5) (an optional separator between letter and
		digit is supported – a.1-a.5 or a-1-a-5)
		digit is supported – a.1-a.5 or a-1-a-5)
	digitletter - digitletter (5a - 5d) (an optional separator between letter and
	digitletter - digitletter (5a - 5d) (an optional separator between letter and
		digit is supported – 5.a-5.d or 5-a-5-d)
		digit is supported – 5.a-5.d or 5-a-5-d)
any other forms are returned unmodified.
any other forms are returned unmodified.
str may be a comma- or semicolon-separated list
str may be a comma- or semicolon-separated list
]]
]]
function p.hyphen_to_dash( str, spacing )
function p.hyphen_to_dash( str, spacing )
	if (str == nil or str == '') then
	if (str == nil or str == '') then
		return str
		return str
	end
	end
	local accept
	local accept
	str = mw.text.decode(str, true )											-- replace html entities with their characters; semicolon mucks up the text.split
	str = mw.text.decode(str, true )											-- replace html entities with their characters; semicolon mucks up the text.split
	local out = {}
	local out = {}
	local list = mw.text.split (str, '%s*[,;]%s*')								-- split str at comma or semicolon separators if there are any
	local list = mw.text.split (str, '%s*[,;]%s*')								-- split str at comma or semicolon separators if there are any
	for _, item in ipairs (list) do												-- for each item in the list
	for _, item in ipairs (list) do												-- for each item in the list
		item = mw.text.trim(item)												-- trim whitespace
		item = mw.text.trim(item)												-- trim whitespace
		item, accept = item:gsub ('^%(%((.+)%)%)$', '%1')
		item, accept = item:gsub ('^%(%((.+)%)%)$', '%1')
		if accept == 0 and mw.ustring.match (item, '^%w*[%.%-]?%w+%s*[%-–—]%s*%w*[%.%-]?%w+$') then	-- if a hyphenated range or has endash or emdash separators
		if accept == 0 and mw.ustring.match (item, '^%w*[%.%-]?%w+%s*[%-–—]%s*%w*[%.%-]?%w+$') then	-- if a hyphenated range or has endash or emdash separators
			if item:match ('^%a+[%.%-]?%d+%s*%-%s*%a+[%.%-]?%d+$') or			-- letterdigit hyphen letterdigit (optional separator between letter and digit)
			if item:match ('^%a+[%.%-]?%d+%s*%-%s*%a+[%.%-]?%d+$') or			-- letterdigit hyphen letterdigit (optional separator between letter and digit)
				item:match ('^%d+[%.%-]?%a+%s*%-%s*%d+[%.%-]?%a+$') or			-- digitletter hyphen digitletter (optional separator between digit and letter)
				item:match ('^%d+[%.%-]?%a+%s*%-%s*%d+[%.%-]?%a+$') or			-- digitletter hyphen digitletter (optional separator between digit and letter)
				item:match ('^%d+[%.%-]%d+%s*%-%s*%d+[%.%-]%d+$') or			-- digit separator digit hyphen digit separator digit
				item:match ('^%d+[%.%-]%d+%s*%-%s*%d+[%.%-]%d+$') or			-- digit separator digit hyphen digit separator digit
				item:match ('^%d+%s*%-%s*%d+$') or								-- digit hyphen digit
				item:match ('^%d+%s*%-%s*%d+$') or								-- digit hyphen digit
				item:match ('^%a+%s*%-%s*%a+$') then							-- letter hyphen letter
				item:match ('^%a+%s*%-%s*%a+$') then							-- letter hyphen letter
					item = item:gsub ('(%w*[%.%-]?%w+)%s*%-%s*(%w*[%.%-]?%w+)', '%1–%2')	-- replace hyphen, remove extraneous space characters
					item = item:gsub ('(%w*[%.%-]?%w+)%s*%-%s*(%w*[%.%-]?%w+)', '%1–%2')	-- replace hyphen, remove extraneous space characters
			else
			else
				item = mw.ustring.gsub (item, '%s*[–—]%s*', '–')				-- for endash or emdash separated ranges, replace em with en, remove extraneous whitespace
				item = mw.ustring.gsub (item, '%s*[–—]%s*', '–')				-- for endash or emdash separated ranges, replace em with en, remove extraneous whitespace
			end
			end
		end
		end
		table.insert (out, item)												-- add the (possibly modified) item to the output table
		table.insert (out, item)												-- add the (possibly modified) item to the output table
	end
	end
	local temp_str = table.concat (out, ',' .. spacing)							-- concatenate the output table into a comma separated string
	local temp_str = table.concat (out, ',' .. spacing)							-- concatenate the output table into a comma separated string
	temp_str, accept = temp_str:gsub ('^%(%((.+)%)%)$', '%1')					-- remove accept-this-as-written markup when it wraps all of concatenated out
	temp_str, accept = temp_str:gsub ('^%(%((.+)%)%)$', '%1')					-- remove accept-this-as-written markup when it wraps all of concatenated out
	if accept ~= 0 then
	if accept ~= 0 then
		temp_str = str:gsub ('^%(%((.+)%)%)$', '%1')							-- when global markup removed, return original str; do it this way to suppress boolean second return value
		temp_str = str:gsub ('^%(%((.+)%)%)$', '%1')							-- when global markup removed, return original str; do it this way to suppress boolean second return value
	end
	end
	return temp_str
	return temp_str
end
end
function p.hyphen2dash( frame )
function p.hyphen2dash( frame )
	local str = frame.args[1] or ''
	local str = frame.args[1] or ''
	local spacing = frame.args[2] or ' ' -- space is part of the standard separator for normal spacing (but in conjunction with templates r/rp/ran we may need a narrower spacing
	local spacing = frame.args[2] or ' ' -- space is part of the standard separator for normal spacing (but in conjunction with templates r/rp/ran we may need a narrower spacing
	return p.hyphen_to_dash(str, spacing)
	return p.hyphen_to_dash(str, spacing)
end
end
-- Similar to [[Module:String#endswith]]
-- Similar to [[Module:String#endswith]]
function p.startswith(frame)
function p.startswith(frame)
	return (frame.args[1]:sub(1, frame.args[2]:len()) == frame.args[2]) and 'yes' or ''
	return (frame.args[1]:sub(1, frame.args[2]:len()) == frame.args[2]) and 'yes' or ''
end
end
return p
return p