<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://test.st34an.tech/index.php?action=history&amp;feed=atom&amp;title=Module%3AString_utilities</id>
	<title>Module:String utilities - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://test.st34an.tech/index.php?action=history&amp;feed=atom&amp;title=Module%3AString_utilities"/>
	<link rel="alternate" type="text/html" href="https://test.st34an.tech/index.php?title=Module:String_utilities&amp;action=history"/>
	<updated>2026-04-10T17:42:36Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.45.3</generator>
	<entry>
		<id>https://test.st34an.tech/index.php?title=Module:String_utilities&amp;diff=35&amp;oldid=prev</id>
		<title>Jsrs701: 1 revision imported</title>
		<link rel="alternate" type="text/html" href="https://test.st34an.tech/index.php?title=Module:String_utilities&amp;diff=35&amp;oldid=prev"/>
		<updated>2026-04-10T07:25:48Z</updated>

		<summary type="html">&lt;p&gt;1 revision imported&lt;/p&gt;
&lt;table style=&quot;background-color: #fff; color: #202122;&quot; data-mw=&quot;interface&quot;&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;tr class=&quot;diff-title&quot; lang=&quot;en&quot;&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;← Older revision&lt;/td&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;Revision as of 07:25, 10 April 2026&lt;/td&gt;
				&lt;/tr&gt;&lt;tr&gt;&lt;td colspan=&quot;4&quot; class=&quot;diff-notice&quot; lang=&quot;en&quot;&gt;&lt;div class=&quot;mw-diff-empty&quot;&gt;(No difference)&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;
&lt;!-- diff cache key mediawikidb:diff:1.41:old-34:rev-35 --&gt;
&lt;/table&gt;</summary>
		<author><name>Jsrs701</name></author>
	</entry>
	<entry>
		<id>https://test.st34an.tech/index.php?title=Module:String_utilities&amp;diff=34&amp;oldid=prev</id>
		<title>bob&gt;Djpwikiadmin at 00:42, 7 September 2023</title>
		<link rel="alternate" type="text/html" href="https://test.st34an.tech/index.php?title=Module:String_utilities&amp;diff=34&amp;oldid=prev"/>
		<updated>2023-09-07T00:42:08Z</updated>

		<summary type="html">&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;local module_name = &amp;quot;string_utilities&amp;quot;&lt;br /&gt;
local export = {}&lt;br /&gt;
&lt;br /&gt;
local format_escapes = {&lt;br /&gt;
    [&amp;quot;op&amp;quot;] = &amp;quot;{&amp;quot;,&lt;br /&gt;
    [&amp;quot;cl&amp;quot;] = &amp;quot;}&amp;quot;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function export.format_fun(str, fun)&lt;br /&gt;
    return (str:gsub(&amp;quot;{(\\?)((\\?)[^{}]*)}&amp;quot;, function (p1, name, p2)&lt;br /&gt;
        if #p1 + #p2 == 1 then&lt;br /&gt;
            return format_escapes[name] or error(module_name .. &amp;quot;.format: unrecognized escape sequence &amp;#039;{\\&amp;quot; .. name .. &amp;quot;}&amp;#039;&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
        	if fun(name) and type(fun(name)) ~= &amp;quot;string&amp;quot; then&lt;br /&gt;
        		error(module_name .. &amp;quot;.format: &amp;#039;&amp;quot; .. name .. &amp;quot;&amp;#039; is a &amp;quot; .. type(fun(name)) .. &amp;quot;, not a string&amp;quot;)&lt;br /&gt;
        	end&lt;br /&gt;
            return fun(name) or error(module_name .. &amp;quot;.format: &amp;#039;&amp;quot; .. name .. &amp;quot;&amp;#039; not found in table&amp;quot;)&lt;br /&gt;
        end&lt;br /&gt;
    end))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[==[This function, unlike {{code|lua|string.format}} and {{code|lua|mw.ustring.format}}, takes just two parameters—a format string and a table—and replaces all instances of {{code|lua|{param_name}}} in the format string with the table&amp;#039;s entry for {{code|lua|param_name}}. The opening and closing brace characters can be escaped with &amp;lt;code&amp;gt;{\op}&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;{\cl}&amp;lt;/code&amp;gt;, respectively. A table entry beginning with a slash can be escaped by doubling the initial slash.&lt;br /&gt;
====Examples====&lt;br /&gt;
* {{code|lua|2=string_utilities.format(&amp;quot;{foo} fish, {bar} fish, {baz} fish, {quux} fish&amp;quot;, {[&amp;quot;foo&amp;quot;]=&amp;quot;one&amp;quot;, [&amp;quot;bar&amp;quot;]=&amp;quot;two&amp;quot;, [&amp;quot;baz&amp;quot;]=&amp;quot;red&amp;quot;, [&amp;quot;quux&amp;quot;]=&amp;quot;blue&amp;quot;})}}&lt;br /&gt;
*: produces: {{code|lua|&amp;quot;one fish, two fish, red fish, blue fish&amp;quot;}}&lt;br /&gt;
* {{code|lua|2=string_utilities.format(&amp;quot;The set {\\op}1, 2, 3{\\cl} contains {\\\\hello} elements.&amp;quot;, {[&amp;quot;\\hello&amp;quot;]=&amp;quot;three&amp;quot;})}}&lt;br /&gt;
*: produces: {{code|lua|&amp;quot;The set {1, 2, 3} contains three elements.&amp;quot;}}&lt;br /&gt;
*:* Note that the single and double backslashes should be entered as double and quadruple backslashes when quoted in a literal string.]==]&lt;br /&gt;
function export.format(str, tbl)&lt;br /&gt;
    return export.format_fun(str, function (key) return tbl[key] end)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- A helper function which takes a string, position and type (&amp;quot;byte&amp;quot; or &amp;quot;char&amp;quot;), and returns the equivalent position for the other type (e.g. iterate_utf8(&amp;quot;字典&amp;quot;, 2, &amp;quot;char&amp;quot;) returns 4, because character 2 of &amp;quot;字典&amp;quot; begins with byte 4). `pos` can be positive or negative, and the function will iterate over the string forwards or backwards (respectively) until it reaches the input position. Checks byte-by-byte; skipping over trailing bytes, and then calculating the correct byte trail for any leading bytes (i.e. how many trailing bytes should follow); these trailing bytes are then checked together.&lt;br /&gt;
-- The optional parameters `init_from_type` and `init_to_type` can be used to start part-way through an iteration to improve performance, if multiple values need to be returned from the same string. For example, iterate_utf8(&amp;quot;слова́рь&amp;quot;, 11, &amp;quot;byte&amp;quot;, 5, 3) will begin checking at byte 5/the start of character 3. Note: The function won&amp;#039;t check if these values match each other (as the only way to do this would be to run the iteration from the beginning), so mismatched values will return incorrect results.&lt;br /&gt;
local function iterate_utf8(text, pos, from_type, init_from_type, init_to_type)&lt;br /&gt;
	-- Position 0 is always valid and never changes.&lt;br /&gt;
	if pos == 0 then&lt;br /&gt;
		return pos&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	local to_type&lt;br /&gt;
	if from_type == &amp;quot;char&amp;quot; then&lt;br /&gt;
		to_type = &amp;quot;byte&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		to_type = &amp;quot;char&amp;quot;&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	-- Positive positions iterate forwards; negative positions iterate backwards.&lt;br /&gt;
	local iterate_val&lt;br /&gt;
	if pos &amp;gt; 0 then&lt;br /&gt;
		iterate_val = 1&lt;br /&gt;
	else&lt;br /&gt;
		iterate_val = -1&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	-- Adjust init_from_type and init_to_type to the iteration before, so that matches for the position given by them will work.&lt;br /&gt;
	local trail, cp, min, b = 0&lt;br /&gt;
	local c, leading_byte = {}&lt;br /&gt;
	c[from_type] = init_from_type and init_from_type ~= 0 and init_from_type - iterate_val or 0&lt;br /&gt;
	c[to_type] = init_to_type and init_to_type ~= 0 and init_to_type - iterate_val or 0&lt;br /&gt;
	&lt;br /&gt;
	while true do&lt;br /&gt;
		if pos &amp;gt; 0 then&lt;br /&gt;
			b = text:byte(c.byte + 1)&lt;br /&gt;
		else&lt;br /&gt;
			b = text:byte(text:len() + c.byte)&lt;br /&gt;
		end&lt;br /&gt;
		-- Position byte doesn&amp;#039;t exist, so iterate the return value and return it.&lt;br /&gt;
		if not b then&lt;br /&gt;
			return c[to_type] + iterate_val&lt;br /&gt;
		elseif b &amp;lt; 0x80 then&lt;br /&gt;
			-- 1-byte codepoint, 00-7F.&lt;br /&gt;
			trail = 0&lt;br /&gt;
			cp = b&lt;br /&gt;
			min = 0&lt;br /&gt;
			leading_byte = true&lt;br /&gt;
		elseif b &amp;lt; 0xc0 then&lt;br /&gt;
			-- A trailing byte.&lt;br /&gt;
			leading_byte = false&lt;br /&gt;
		elseif b &amp;lt; 0xc2 then&lt;br /&gt;
			-- An overlong encoding for a 1-byte codepoint.&lt;br /&gt;
			error(&amp;quot;String &amp;quot; .. text .. &amp;quot; is not UTF-8.&amp;quot;)&lt;br /&gt;
		elseif b &amp;lt; 0xe0 then&lt;br /&gt;
			-- 2-byte codepoint, C2-DF.&lt;br /&gt;
			trail = 1&lt;br /&gt;
			cp = b - 0xc0&lt;br /&gt;
			min = 0x80&lt;br /&gt;
			leading_byte = true&lt;br /&gt;
		elseif b &amp;lt; 0xf0 then&lt;br /&gt;
			-- 3-byte codepoint, E0-EF.&lt;br /&gt;
			trail = 2&lt;br /&gt;
			cp = b - 0xe0&lt;br /&gt;
			min = 0x800&lt;br /&gt;
			leading_byte = true&lt;br /&gt;
		elseif b &amp;lt; 0xf4 then&lt;br /&gt;
			-- 4-byte codepoint, F0-F3.&lt;br /&gt;
			trail = 3&lt;br /&gt;
			cp = b - 0xf0&lt;br /&gt;
			min = 0x10000&lt;br /&gt;
			leading_byte = true&lt;br /&gt;
		elseif b == 0xf4 then&lt;br /&gt;
			-- 4-byte codepoint, F4.&lt;br /&gt;
			-- Make sure it doesn&amp;#039;t decode to over U+10FFFF.&lt;br /&gt;
			if text:byte(c.byte + 2) &amp;gt; 0x8f then&lt;br /&gt;
				error(&amp;quot;String &amp;quot; .. text .. &amp;quot; is not UTF-8.&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
			trail = 3&lt;br /&gt;
			cp = 4&lt;br /&gt;
			min = 0x100000&lt;br /&gt;
			leading_byte = true&lt;br /&gt;
		else&lt;br /&gt;
			-- Codepoint over U+10FFFF, or invalid byte.&lt;br /&gt;
			error(&amp;quot;String &amp;quot; .. text .. &amp;quot; is not UTF-8.&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		-- Check subsequent bytes for multibyte codepoints.&lt;br /&gt;
		if leading_byte then&lt;br /&gt;
			local from, to&lt;br /&gt;
			if pos &amp;gt; 0 then&lt;br /&gt;
				from, to = c.byte + 2, c.byte + 1 + trail&lt;br /&gt;
			else&lt;br /&gt;
				from, to = text:len() + c.byte + 1, text:len() + c.byte + trail&lt;br /&gt;
			end&lt;br /&gt;
			for trailing_byte = from, to do&lt;br /&gt;
				b = text:byte(trailing_byte)&lt;br /&gt;
				if not b or b &amp;lt; 0x80 or b &amp;gt; 0xbf then&lt;br /&gt;
					error(&amp;quot;String &amp;quot; .. text .. &amp;quot; is not UTF-8.&amp;quot;)&lt;br /&gt;
				end&lt;br /&gt;
				cp = cp * 0x40 + b - 0x80&lt;br /&gt;
			end&lt;br /&gt;
			local next_byte = text:byte(to + 1)&lt;br /&gt;
			if next_byte and next_byte &amp;gt;= 0x80 and next_byte &amp;lt;= 0xbf then&lt;br /&gt;
				-- Too many trailing bytes.&lt;br /&gt;
				error(&amp;quot;String &amp;quot; .. text .. &amp;quot; is not UTF-8.&amp;quot;)&lt;br /&gt;
			elseif cp &amp;lt; min then&lt;br /&gt;
				-- Overlong encoding.&lt;br /&gt;
				error(&amp;quot;String &amp;quot; .. text .. &amp;quot; is not UTF-8.&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		c.byte = c.byte + iterate_val&lt;br /&gt;
		if leading_byte then&lt;br /&gt;
			c.char = c.char + iterate_val&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		if c[from_type] == pos then&lt;br /&gt;
			return c[to_type]&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[==[Converts a character position to the equivalent byte position.]==]&lt;br /&gt;
function export.charsToBytes(text, pos)&lt;br /&gt;
	return iterate_utf8(text, pos, &amp;quot;char&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[==[Converts a byte position to the equivalent character position.]==]&lt;br /&gt;
function export.bytesToChars(text, pos)&lt;br /&gt;
	local byte = text:byte(pos)&lt;br /&gt;
	if byte and byte &amp;gt;= 0x80 and byte &amp;lt;= 0xbf then&lt;br /&gt;
		error(&amp;quot;Byte &amp;quot; .. pos .. &amp;quot; is not a leading byte.&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	return iterate_utf8(text, pos, &amp;quot;byte&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- A helper function which iterates through a pattern, and returns two values: a potentially modified version of the pattern, and a boolean indicating whether the returned pattern is simple (i.e. whether it can be used with the stock string library); if not, then the pattern is complex (i.e. it must be used with the ustring library, which is much more resource-intensive).&lt;br /&gt;
local function patternSimplifier(text, pattern, plain)&lt;br /&gt;
	pattern = tostring(pattern)&lt;br /&gt;
	-- If `plain` is set, then the pattern is treated as literal (so is always simple). Only used by find.&lt;br /&gt;
	if plain then&lt;br /&gt;
		return pattern, true&lt;br /&gt;
	--If none of these are present, then the pattern has to be simple.&lt;br /&gt;
	elseif not (&lt;br /&gt;
		pattern:match(&amp;quot;%[.-[\128-\255].-%]&amp;quot;) or&lt;br /&gt;
		pattern:match(&amp;quot;[\128-\255][%*%+%?%-]&amp;quot;) or&lt;br /&gt;
		pattern:match(&amp;quot;%%[abcdlpsuwxACDLPSUWXZ]&amp;quot;) or&lt;br /&gt;
		pattern:match(&amp;quot;%[%^[^%]]+%]&amp;quot;) or&lt;br /&gt;
		pattern:match(&amp;quot;%.[^%*%+%-]&amp;quot;) or&lt;br /&gt;
		pattern:match(&amp;quot;%.$&amp;quot;) or&lt;br /&gt;
		pattern:match(&amp;quot;%%b.?[\128-\255]&amp;quot;) or&lt;br /&gt;
		pattern:match(&amp;quot;()&amp;quot;, 1, true)&lt;br /&gt;
	) then&lt;br /&gt;
		return pattern, true&lt;br /&gt;
	end&lt;br /&gt;
	-- Otherwise, the pattern could go either way.&lt;br /&gt;
	-- Build up the new pattern in a table, then concatenate at the end. we do it this way, as occasionally entries get modified along the way.&lt;br /&gt;
	local new_pattern = {}&lt;br /&gt;
	local len, pos, b = pattern:len(), 0&lt;br /&gt;
	local char, next_char&lt;br /&gt;
	&lt;br /&gt;
	-- `escape` and `balanced` are counters, which ensure the effects of % or %b (respectively) are distributed over the following bytes.&lt;br /&gt;
	-- `set` is a boolean that states whether the current byte is in a charset.&lt;br /&gt;
	-- `capture` keeps track of how many layers of capture groups the position is in, while `captures` keeps a tally of how many groups have been detected (due to the string library limit of 32).&lt;br /&gt;
	local escape, set, balanced, capture, captures = 0, false, 0, 0, 0&lt;br /&gt;
	&lt;br /&gt;
	while pos &amp;lt; len do&lt;br /&gt;
		pos = pos + 1&lt;br /&gt;
		b = pattern:byte(pos)&lt;br /&gt;
		if escape &amp;gt; 0 then escape = escape - 1 end&lt;br /&gt;
		if balanced &amp;gt; 0 then balanced = balanced - 1 end&lt;br /&gt;
		char = next_char or pattern:sub(pos, pos)&lt;br /&gt;
		next_char = pattern:sub(pos + 1, pos + 1)&lt;br /&gt;
		if escape == 0 then&lt;br /&gt;
			if char == &amp;quot;%&amp;quot; then&lt;br /&gt;
				-- Apply % escape.&lt;br /&gt;
				if next_char == &amp;quot;.&amp;quot; or next_char == &amp;quot;%&amp;quot; or next_char == &amp;quot;[&amp;quot; or next_char == &amp;quot;]&amp;quot; then&lt;br /&gt;
					escape = 2&lt;br /&gt;
					if balanced &amp;gt; 0 then balanced = balanced + 1 end&lt;br /&gt;
				-- These charsets make the pattern complex.&lt;br /&gt;
				elseif next_char:match(&amp;quot;[acdlpsuwxACDLPSUWXZ]&amp;quot;) then&lt;br /&gt;
					return pattern, false&lt;br /&gt;
				-- This is &amp;quot;%b&amp;quot;.&lt;br /&gt;
				elseif next_char == &amp;quot;b&amp;quot; then&lt;br /&gt;
					balanced = 4&lt;br /&gt;
				end&lt;br /&gt;
			-- Enter or leave a charset.&lt;br /&gt;
			elseif char == &amp;quot;[&amp;quot; then&lt;br /&gt;
				set = true&lt;br /&gt;
			elseif char == &amp;quot;]&amp;quot; then&lt;br /&gt;
				set = false&lt;br /&gt;
			elseif char == &amp;quot;(&amp;quot; then&lt;br /&gt;
				capture = capture + 1&lt;br /&gt;
			elseif char == &amp;quot;)&amp;quot; then&lt;br /&gt;
				if capture &amp;gt; 0 and set == false and balanced == 0 then&lt;br /&gt;
					captures = captures + 1&lt;br /&gt;
					capture = capture - 1&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		-- Multibyte char.&lt;br /&gt;
		if b &amp;gt; 0x7f then&lt;br /&gt;
			-- If followed by &amp;quot;*&amp;quot;, &amp;quot;+&amp;quot; or &amp;quot;-&amp;quot;, then 2-byte chars can be converted into charsets. However, this is not possible with 3 or 4-byte chars, as the charset would be too permissive, because if the trailing bytes were in a different order then this could be a different valid character.&lt;br /&gt;
			if next_char == &amp;quot;*&amp;quot; or next_char == &amp;quot;+&amp;quot; or next_char == &amp;quot;-&amp;quot; then&lt;br /&gt;
				local prev_pos = pattern:byte(pos - 1)&lt;br /&gt;
				if prev_pos &amp;gt; 0xc1 and prev_pos &amp;lt; 0xe0 then&lt;br /&gt;
					new_pattern[#new_pattern] = &amp;quot;[&amp;quot; .. new_pattern[#new_pattern]&lt;br /&gt;
					table.insert(new_pattern, char .. &amp;quot;]&amp;quot;)&lt;br /&gt;
				else&lt;br /&gt;
					return pattern, false&lt;br /&gt;
				end&lt;br /&gt;
			-- If in a charset or used in &amp;quot;%b&amp;quot;, then the pattern is complex.&lt;br /&gt;
			-- If followed by &amp;quot;?&amp;quot;, add &amp;quot;?&amp;quot; after each byte.&lt;br /&gt;
			elseif next_char == &amp;quot;?&amp;quot; then&lt;br /&gt;
				table.insert(new_pattern, char .. &amp;quot;?&amp;quot;)&lt;br /&gt;
				local check_pos, check_b, i = pos, pattern:byte(pos), #new_pattern&lt;br /&gt;
				while check_b and check_b &amp;lt; 0xc0 do&lt;br /&gt;
					check_pos = check_pos - 1&lt;br /&gt;
					check_b = pattern:byte(check_pos)&lt;br /&gt;
					i = i - 1&lt;br /&gt;
					new_pattern[i] = new_pattern[i] .. &amp;quot;?&amp;quot;&lt;br /&gt;
				end&lt;br /&gt;
				pos = pos + 1&lt;br /&gt;
				next_char = pattern:sub(pos + 1, pos + 1)&lt;br /&gt;
			elseif set or balanced &amp;gt; 0 then&lt;br /&gt;
				return pattern, false&lt;br /&gt;
			else&lt;br /&gt;
				table.insert(new_pattern, char)&lt;br /&gt;
			end&lt;br /&gt;
		elseif char == &amp;quot;.&amp;quot; then&lt;br /&gt;
			-- &amp;quot;*&amp;quot;, &amp;quot;+&amp;quot;, &amp;quot;-&amp;quot; are always okay after &amp;quot;.&amp;quot;, as they don&amp;#039;t care how many bytes a char has.&lt;br /&gt;
			if set or next_char == &amp;quot;*&amp;quot; or next_char == &amp;quot;+&amp;quot; or next_char == &amp;quot;-&amp;quot; or escape &amp;gt; 0 then&lt;br /&gt;
				table.insert(new_pattern, char)&lt;br /&gt;
			-- If followed by &amp;quot;?&amp;quot;, make sure &amp;quot;?&amp;quot; is after the leading byte of the UTF-8 char pattern, then skip forward one.&lt;br /&gt;
			elseif next_char == &amp;quot;?&amp;quot; then&lt;br /&gt;
				table.insert(new_pattern, &amp;quot;[%z\1-\127\194-\244]?[\128-\191]*&amp;quot;)&lt;br /&gt;
				pos = pos + 1&lt;br /&gt;
				next_char = pattern:sub(pos + 1, pos + 1)&lt;br /&gt;
			-- If used with &amp;quot;%b&amp;quot;, pattern is complex.&lt;br /&gt;
			elseif balanced &amp;gt; 0 then&lt;br /&gt;
				return pattern, false&lt;br /&gt;
			-- Otherwise, add the UTF-8 char pattern.&lt;br /&gt;
			else&lt;br /&gt;
				table.insert(new_pattern, &amp;quot;[%z\1-\127\194-\244][\128-\191]*&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
		-- Negative charsets are always complex, unless the text has no UTF-8 chars.&lt;br /&gt;
		elseif char == &amp;quot;[&amp;quot; and next_char == &amp;quot;^&amp;quot; and escape == 0 and text:match(&amp;quot;[\128-\255]&amp;quot;) then&lt;br /&gt;
			return pattern, false&lt;br /&gt;
		-- &amp;quot;()&amp;quot; matches the position unless escaped or used with &amp;quot;%b&amp;quot;, so always necessitates ustring (as we need it to match the char position, not the byte one).&lt;br /&gt;
		elseif char == &amp;quot;(&amp;quot; and next_char == &amp;quot;)&amp;quot; and balanced == 0 and escape == 0 and text:match(&amp;quot;[\128-\255]&amp;quot;) then&lt;br /&gt;
			return pattern, false&lt;br /&gt;
		else&lt;br /&gt;
			table.insert(new_pattern, char)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if captures &amp;gt; 32 then&lt;br /&gt;
		return pattern, false&lt;br /&gt;
	else&lt;br /&gt;
		pattern = table.concat(new_pattern)&lt;br /&gt;
		return pattern, true&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[==[A version of len which uses string.len, but returns the same result as mw.ustring.len.]==]&lt;br /&gt;
function export.len(text)&lt;br /&gt;
	text = tostring(text)&lt;br /&gt;
	local len_bytes = text:len()&lt;br /&gt;
	if not text:match(&amp;quot;[\128-\255]&amp;quot;) then&lt;br /&gt;
		return len_bytes&lt;br /&gt;
	else&lt;br /&gt;
		return iterate_utf8(text, len_bytes, &amp;quot;byte&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[==[A version of sub which uses string.sub, but returns the same result as mw.ustring.sub.]==]&lt;br /&gt;
function export.sub(text, i_char, j_char)&lt;br /&gt;
	text = tostring(text)&lt;br /&gt;
	if not text:match(&amp;quot;[\128-\255]&amp;quot;) then&lt;br /&gt;
		return text:sub(i_char, j_char)&lt;br /&gt;
	end&lt;br /&gt;
	local i_byte, j_byte&lt;br /&gt;
	if j_char then&lt;br /&gt;
		if i_char &amp;gt; 0 and j_char &amp;gt; 0 then&lt;br /&gt;
			if j_char &amp;lt; i_char then return &amp;quot;&amp;quot; end&lt;br /&gt;
			i_byte = iterate_utf8(text, i_char, &amp;quot;char&amp;quot;)&lt;br /&gt;
			j_byte = iterate_utf8(text, j_char + 1, &amp;quot;char&amp;quot;, i_char, i_byte) - 1&lt;br /&gt;
		elseif i_char &amp;lt; 0 and j_char &amp;lt; 0 then&lt;br /&gt;
			if j_char &amp;lt; i_char then return &amp;quot;&amp;quot; end&lt;br /&gt;
			j_byte = iterate_utf8(text, j_char + 1, &amp;quot;char&amp;quot;) - 1&lt;br /&gt;
			i_byte = iterate_utf8(text, i_char, &amp;quot;char&amp;quot;, j_char, j_byte)&lt;br /&gt;
		-- For some reason, mw.ustring.sub with i=0, j=0 returns the same result as for i=1, j=1, while string.sub always returns &amp;quot;&amp;quot;. However, mw.ustring.sub does return &amp;quot;&amp;quot; with i=1, j=0. As such, we need to adjust j_char to 1 if i_char is either 0, or negative with a magnitude greater than the length of the string.&lt;br /&gt;
		elseif j_char == 0 then&lt;br /&gt;
			i_byte = iterate_utf8(text, i_char, &amp;quot;char&amp;quot;)&lt;br /&gt;
			if i_byte == 0 or -i_byte &amp;gt; text:len() then j_char = 1 end&lt;br /&gt;
			j_byte = iterate_utf8(text, j_char + 1, &amp;quot;char&amp;quot;) - 1&lt;br /&gt;
		else&lt;br /&gt;
			i_byte = iterate_utf8(text, i_char, &amp;quot;char&amp;quot;)&lt;br /&gt;
			j_byte = iterate_utf8(text, j_char + 1, &amp;quot;char&amp;quot;) - 1&lt;br /&gt;
		end&lt;br /&gt;
	else&lt;br /&gt;
		i_byte = iterate_utf8(text, i_char, &amp;quot;char&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	return text:sub(i_byte, j_byte)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[==[A version of lower which uses string.lower when possible, but otherwise uses mw.ustring.lower.]==]&lt;br /&gt;
function export.lower(text)&lt;br /&gt;
	text = tostring(text)&lt;br /&gt;
	if not text:match(&amp;quot;[\128-\255]&amp;quot;) then&lt;br /&gt;
		return text:lower()&lt;br /&gt;
	else&lt;br /&gt;
		return mw.ustring.lower(text)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[==[A version of upper which uses string.upper when possible, but otherwise uses mw.ustring.upper.]==]&lt;br /&gt;
function export.upper(text)&lt;br /&gt;
	text = tostring(text)&lt;br /&gt;
	if not text:match(&amp;quot;[\128-\255]&amp;quot;) then&lt;br /&gt;
		return text:upper()&lt;br /&gt;
	else&lt;br /&gt;
		return mw.ustring.upper(text)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[==[A version of find which uses string.find when possible, but otherwise uses mw.ustring.find.]==]&lt;br /&gt;
function export.find(text, pattern, init_char, plain)&lt;br /&gt;
	text = tostring(text)&lt;br /&gt;
	local simple&lt;br /&gt;
	pattern, simple = patternSimplifier(text, pattern, plain)&lt;br /&gt;
	-- If the pattern is simple but multibyte characters are present, then init_char needs to be converted into bytes for string.find to work properly, and the return values need to be converted back into chars.&lt;br /&gt;
	if simple then&lt;br /&gt;
		if not text:match(&amp;quot;[\128-\255]&amp;quot;) then&lt;br /&gt;
			return text:find(pattern, init_char, plain)&lt;br /&gt;
		else&lt;br /&gt;
			local init_byte = init_char and iterate_utf8(text, init_char, &amp;quot;char&amp;quot;)&lt;br /&gt;
			local byte1, byte2, c1, c2, c3, c4, c5, c6, c7, c8, c9 = text:find(pattern, init_byte, plain)&lt;br /&gt;
			&lt;br /&gt;
			-- If string.find returned nil, then return nil.&lt;br /&gt;
			if not (byte1 and byte2) then&lt;br /&gt;
				return nil&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
			-- Get first return value. If we have a positive init_char, we can save resources by resuming at that point.&lt;br /&gt;
			local char1, char2&lt;br /&gt;
			if (not init_char) or init_char &amp;gt; 0 then&lt;br /&gt;
				char1 = iterate_utf8(text, byte1, &amp;quot;byte&amp;quot;, init_byte, init_char)&lt;br /&gt;
			else&lt;br /&gt;
				char1 = iterate_utf8(text, byte1, &amp;quot;byte&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
			-- If byte1 and byte2 are the same, don&amp;#039;t bother running iterate_utf8 twice. Otherwise, resume iterate_utf8 from byte1 to find char2.&lt;br /&gt;
			if byte1 == byte2 then&lt;br /&gt;
				char2 = char1&lt;br /&gt;
			else&lt;br /&gt;
				char2 = iterate_utf8(text, byte2, &amp;quot;byte&amp;quot;, byte1, char1)&lt;br /&gt;
			end&lt;br /&gt;
			&lt;br /&gt;
			return unpack{char1, char2, c1, c2, c3, c4, c5, c6, c7, c8, c9}&lt;br /&gt;
		end&lt;br /&gt;
	else&lt;br /&gt;
		return mw.ustring.find(text, pattern, init_char, plain)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[==[A version of match which uses string.match when possible, but otherwise uses mw.ustring.match.]==]&lt;br /&gt;
function export.match(text, pattern, init)&lt;br /&gt;
	text = tostring(text)&lt;br /&gt;
	local simple&lt;br /&gt;
	pattern, simple = patternSimplifier(text, pattern)&lt;br /&gt;
	if simple then&lt;br /&gt;
		if init and text:find(&amp;quot;[\128-\255]&amp;quot;) then&lt;br /&gt;
			init = iterate_utf8(text, init, &amp;quot;char&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
		return text:match(pattern, init)&lt;br /&gt;
	else&lt;br /&gt;
		return mw.ustring.match(text, pattern, init)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[==[A version of gmatch which uses string.gmatch when possible, but otherwise uses mw.ustring.gmatch.]==]&lt;br /&gt;
function export.gmatch(text, pattern)&lt;br /&gt;
	text = tostring(text)&lt;br /&gt;
	local simple&lt;br /&gt;
	pattern, simple = patternSimplifier(text, pattern)&lt;br /&gt;
	if simple then&lt;br /&gt;
		return text:gmatch(pattern)&lt;br /&gt;
	else&lt;br /&gt;
		return mw.ustring.gmatch(text, pattern)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[==[A version of gsub which uses string.gsub when possible, but otherwise uses mw.ustring.gsub.]==]&lt;br /&gt;
function export.gsub(text, pattern, repl, n)&lt;br /&gt;
	text = tostring(text)&lt;br /&gt;
	local simple&lt;br /&gt;
	pattern, simple = patternSimplifier(text, pattern)&lt;br /&gt;
	if simple then&lt;br /&gt;
		return text:gsub(pattern, repl, n)&lt;br /&gt;
	else&lt;br /&gt;
		return mw.ustring.gsub(text, pattern, repl, n)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[==[&lt;br /&gt;
-- Reimplementation of mw.ustring.split() that includes any capturing&lt;br /&gt;
-- groups in the splitting pattern. This works like Python&amp;#039;s re.split()&lt;br /&gt;
-- function, except that it has Lua&amp;#039;s behavior when the split pattern&lt;br /&gt;
-- is empty (i.e. advancing by one character at a time; Python returns the&lt;br /&gt;
-- whole remainder of the string).&lt;br /&gt;
]==]&lt;br /&gt;
function export.capturing_split(str, pattern)&lt;br /&gt;
    local ret = {}&lt;br /&gt;
    -- (.-) corresponds to (.*?) in Python or Perl; () captures the&lt;br /&gt;
    -- current position after matching.&lt;br /&gt;
    pattern = &amp;quot;(.-)&amp;quot; .. pattern .. &amp;quot;()&amp;quot;&lt;br /&gt;
    local start = 1&lt;br /&gt;
    while true do&lt;br /&gt;
        -- Did we reach the end of the string?&lt;br /&gt;
        if start &amp;gt; #str then&lt;br /&gt;
            table.insert(ret, &amp;quot;&amp;quot;)&lt;br /&gt;
            return ret&lt;br /&gt;
        end&lt;br /&gt;
        -- match() returns all captures as multiple return values;&lt;br /&gt;
        -- we need to insert into a table to get them all.&lt;br /&gt;
        local captures = {export.match(str, pattern, start)}&lt;br /&gt;
        -- If no match, add the remainder of the string.&lt;br /&gt;
        if #captures == 0 then&lt;br /&gt;
            table.insert(ret, export.sub(str, start))&lt;br /&gt;
            return ret&lt;br /&gt;
        end&lt;br /&gt;
        local newstart = table.remove(captures)&lt;br /&gt;
        -- Special case: If we don&amp;#039;t advance by any characters, then advance&lt;br /&gt;
        -- by one character; this avoids an infinite loop, and makes splitting&lt;br /&gt;
        -- by an empty string work the way mw.ustring.split() does. If we&lt;br /&gt;
        -- reach the end of the string this way, return immediately, so we&lt;br /&gt;
        -- don&amp;#039;t get a final empty string.&lt;br /&gt;
        if newstart == start then&lt;br /&gt;
            table.insert(ret, export.sub(str, start, start))&lt;br /&gt;
            table.remove(captures, 1)&lt;br /&gt;
            start = start + 1&lt;br /&gt;
            if start &amp;gt; #str then&lt;br /&gt;
            	return ret&lt;br /&gt;
            end&lt;br /&gt;
        else&lt;br /&gt;
            table.insert(ret, table.remove(captures, 1))&lt;br /&gt;
            start = newstart&lt;br /&gt;
        end&lt;br /&gt;
        -- Insert any captures from the splitting pattern.&lt;br /&gt;
        for _, x in ipairs(captures) do&lt;br /&gt;
            table.insert(ret, x)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function uclcfirst(text, dolower)&lt;br /&gt;
	local function douclcfirst(text)&lt;br /&gt;
		-- Actual function to re-case of the first letter.&lt;br /&gt;
		local first_letter = export.sub(text, 1, 1)&lt;br /&gt;
		first_letter = dolower and export.lower(first_letter) or export.upper(first_letter)&lt;br /&gt;
		return first_letter .. export.sub(text, 2)&lt;br /&gt;
	end&lt;br /&gt;
	-- If there&amp;#039;s a link at the beginning, re-case the first letter of the&lt;br /&gt;
	-- link text. This pattern matches both piped and unpiped links.&lt;br /&gt;
	-- If the link is not piped, the second capture (linktext) will be empty.&lt;br /&gt;
	local link, linktext, remainder = export.match(text, &amp;quot;^%[%[([^|%]]+)%|?(.-)%]%](.*)$&amp;quot;)&lt;br /&gt;
	if link then&lt;br /&gt;
		return &amp;quot;[[&amp;quot; .. link .. &amp;quot;|&amp;quot; .. douclcfirst(linktext ~= &amp;quot;&amp;quot; and linktext or link) .. &amp;quot;]]&amp;quot; .. remainder&lt;br /&gt;
	end&lt;br /&gt;
	return douclcfirst(text)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function export.ucfirst(text)&lt;br /&gt;
	return uclcfirst(text, false)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function export.lcfirst(text)&lt;br /&gt;
	return uclcfirst(text, true)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Almost identical to mw.text.nowiki, but with minor changes to be identical to the PHP equivalent: &amp;quot;;&amp;quot; always escapes, and colons in certain protocols only escape after regex \b. Also about 2-3 times as fast.&lt;br /&gt;
function export.nowiki(text)&lt;br /&gt;
	return (text&lt;br /&gt;
		:gsub(&amp;quot;[\&amp;quot;&amp;amp;&amp;#039;&amp;lt;=&amp;gt;%[%]{|};]&amp;quot;, {&lt;br /&gt;
			[&amp;quot;\&amp;quot;&amp;quot;] = &amp;quot;&amp;amp;#34;&amp;quot;, [&amp;quot;&amp;amp;&amp;quot;] = &amp;quot;&amp;amp;#38;&amp;quot;, [&amp;quot;&amp;#039;&amp;quot;] = &amp;quot;&amp;amp;#39;&amp;quot;,&lt;br /&gt;
			[&amp;quot;&amp;lt;&amp;quot;] = &amp;quot;&amp;amp;#60;&amp;quot;, [&amp;quot;=&amp;quot;] = &amp;quot;&amp;amp;#61;&amp;quot;, [&amp;quot;&amp;gt;&amp;quot;] = &amp;quot;&amp;amp;#62;&amp;quot;,&lt;br /&gt;
			[&amp;quot;[&amp;quot;] = &amp;quot;&amp;amp;#91;&amp;quot;, [&amp;quot;]&amp;quot;] = &amp;quot;&amp;amp;#93;&amp;quot;, [&amp;quot;{&amp;quot;] = &amp;quot;&amp;amp;#123;&amp;quot;,&lt;br /&gt;
			[&amp;quot;|&amp;quot;] = &amp;quot;&amp;amp;#124;&amp;quot;, [&amp;quot;}&amp;quot;] = &amp;quot;&amp;amp;#125;&amp;quot;, [&amp;quot;;&amp;quot;] = &amp;quot;&amp;amp;#59;&amp;quot;&lt;br /&gt;
		})&lt;br /&gt;
		:gsub(&amp;quot;%f[^%z\r\n][#*: \n\r\t]&amp;quot;, {&lt;br /&gt;
			[&amp;quot;#&amp;quot;] = &amp;quot;&amp;amp;#35;&amp;quot;, [&amp;quot;*&amp;quot;] = &amp;quot;&amp;amp;#42;&amp;quot;, [&amp;quot;:&amp;quot;] = &amp;quot;&amp;amp;#58;&amp;quot;,&lt;br /&gt;
			[&amp;quot; &amp;quot;] = &amp;quot;&amp;amp;#32;&amp;quot;, [&amp;quot;\n&amp;quot;] = &amp;quot;&amp;amp;#10;&amp;quot;, [&amp;quot;\r&amp;quot;] = &amp;quot;&amp;amp;#13;&amp;quot;,&lt;br /&gt;
			[&amp;quot;\t&amp;quot;] = &amp;quot;&amp;amp;#9;&amp;quot;&lt;br /&gt;
		})&lt;br /&gt;
		:gsub(&amp;quot;(%f[^%z\r\n])%-(%-%-%-)&amp;quot;, &amp;quot;%1&amp;amp;#45;%2&amp;quot;)&lt;br /&gt;
		:gsub(&amp;quot;__&amp;quot;, &amp;quot;_&amp;amp;#95;&amp;quot;)&lt;br /&gt;
		:gsub(&amp;quot;://&amp;quot;, &amp;quot;&amp;amp;#58;//&amp;quot;)&lt;br /&gt;
		:gsub(&amp;quot;([IP]?[MRS][BFI][CDN])([\t\n\f\r ])&amp;quot;, function(m1, m2)&lt;br /&gt;
			if m1 == &amp;quot;ISBN&amp;quot; or m1 == &amp;quot;RFC&amp;quot; or m1 == &amp;quot;PMID&amp;quot; then&lt;br /&gt;
				return m1 .. m2:gsub(&amp;quot;.&amp;quot;, {&lt;br /&gt;
					[&amp;quot;\t&amp;quot;] = &amp;quot;&amp;amp;#9;&amp;quot;, [&amp;quot;\n&amp;quot;] = &amp;quot;&amp;amp;#10;&amp;quot;, [&amp;quot;\f&amp;quot;] = &amp;quot;&amp;amp;#12;&amp;quot;,&lt;br /&gt;
					[&amp;quot;\r&amp;quot;] = &amp;quot;&amp;amp;#13;&amp;quot;, [&amp;quot; &amp;quot;] = &amp;quot;&amp;amp;#32;&amp;quot;&lt;br /&gt;
				})&lt;br /&gt;
			end&lt;br /&gt;
		end)&lt;br /&gt;
		:gsub(&amp;quot;[%w_]+:&amp;quot;, {&lt;br /&gt;
			[&amp;quot;bitcoin:&amp;quot;] = &amp;quot;bitcoin&amp;amp;#58;&amp;quot;, [&amp;quot;geo:&amp;quot;] = &amp;quot;geo&amp;amp;#58;&amp;quot;, [&amp;quot;magnet:&amp;quot;] = &amp;quot;magnet&amp;amp;#58;&amp;quot;,&lt;br /&gt;
			[&amp;quot;mailto:&amp;quot;] = &amp;quot;mailto&amp;amp;#58;&amp;quot;, [&amp;quot;matrix:&amp;quot;] = &amp;quot;matrix&amp;amp;#58;&amp;quot;, [&amp;quot;news:&amp;quot;] = &amp;quot;news&amp;amp;#58;&amp;quot;,&lt;br /&gt;
			[&amp;quot;sip:&amp;quot;] = &amp;quot;sip&amp;amp;#58;&amp;quot;, [&amp;quot;sips:&amp;quot;] = &amp;quot;sips&amp;amp;#58;&amp;quot;, [&amp;quot;sms:&amp;quot;] = &amp;quot;sms&amp;amp;#58;&amp;quot;,&lt;br /&gt;
			[&amp;quot;tel:&amp;quot;] = &amp;quot;tel&amp;amp;#58;&amp;quot;, [&amp;quot;urn:&amp;quot;] = &amp;quot;urn&amp;amp;#58;&amp;quot;, [&amp;quot;xmpp:&amp;quot;] = &amp;quot;xmpp&amp;amp;#58;&amp;quot;&lt;br /&gt;
		}))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function export.capitalize(text)&lt;br /&gt;
	if type(text) == &amp;quot;table&amp;quot; then&lt;br /&gt;
		-- allow calling from a template&lt;br /&gt;
		text = text.args[1]&lt;br /&gt;
	end&lt;br /&gt;
	-- Capitalize multi-word that is separated by spaces&lt;br /&gt;
	-- by uppercasing the first letter of each part.&lt;br /&gt;
	-- I assume nobody will input all CAP text.&lt;br /&gt;
	w2 = {}&lt;br /&gt;
	for w in export.gmatch(text, &amp;quot;%S+&amp;quot;) do&lt;br /&gt;
		table.insert(w2, uclcfirst(w, false))&lt;br /&gt;
	end&lt;br /&gt;
	return table.concat(w2, &amp;quot; &amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function export.pluralize(text)&lt;br /&gt;
	if type(text) == &amp;quot;table&amp;quot; then&lt;br /&gt;
		-- allow calling from a template&lt;br /&gt;
		text = text.args[1]&lt;br /&gt;
	end&lt;br /&gt;
	-- Pluralize a word in a smart fashion, according to normal English rules.&lt;br /&gt;
	-- 1. If word ends in consonant + -y, replace the -y with -ies.&lt;br /&gt;
	-- 2. If the word ends in -s, -x, -z, -sh, -ch, add -es.&lt;br /&gt;
	-- 3. Otherwise, add -s.&lt;br /&gt;
	-- This handles links correctly:&lt;br /&gt;
	-- 1. If a piped link, change the second part appropriately.&lt;br /&gt;
	-- 2. If a non-piped link and rule #1 above applies, convert to a piped link&lt;br /&gt;
	--    with the second part containing the plural.&lt;br /&gt;
	-- 3. If a non-piped link and rules #2 or #3 above apply, add the plural&lt;br /&gt;
	--    outside the link.&lt;br /&gt;
	&lt;br /&gt;
	local function word_ends_in_consonant_plus_y(text)&lt;br /&gt;
		-- FIXME, a subrule of rule #1 above says the -ies ending doesn&amp;#039;t&lt;br /&gt;
		-- apply to proper nouns, hence &amp;quot;the Gettys&amp;quot;, &amp;quot;the public Ivys&amp;quot;.&lt;br /&gt;
		-- We should maybe consider applying this rule here; but it may not&lt;br /&gt;
		-- be important as this function is almost always called on common nouns&lt;br /&gt;
		-- (e.g. parts of speech, place types).&lt;br /&gt;
		return text:find(&amp;quot;[^aeiouAEIOU ]y$&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	local function word_takes_es_plural(text)&lt;br /&gt;
		return text:find(&amp;quot;[sxz]$&amp;quot;) or text:find(&amp;quot;[cs]h$&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	local function do_pluralize(text)&lt;br /&gt;
		if word_ends_in_consonant_plus_y(text) then&lt;br /&gt;
			-- avoid returning multiple values&lt;br /&gt;
			local hack_single_retval = text:gsub(&amp;quot;y$&amp;quot;, &amp;quot;ies&amp;quot;)&lt;br /&gt;
			return hack_single_retval&lt;br /&gt;
		elseif word_takes_es_plural(text) then&lt;br /&gt;
			return text .. &amp;quot;es&amp;quot;&lt;br /&gt;
		else&lt;br /&gt;
			return text .. &amp;quot;s&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
		&lt;br /&gt;
	-- Check for a link. This pattern matches both piped and unpiped links.&lt;br /&gt;
	-- If the link is not piped, the second capture (linktext) will be empty.&lt;br /&gt;
	local beginning, link, linktext = export.match(text, &amp;quot;^(.*)%[%[([^|%]]+)%|?(.-)%]%]$&amp;quot;)&lt;br /&gt;
	if link then&lt;br /&gt;
		if linktext ~= &amp;quot;&amp;quot; then&lt;br /&gt;
			return beginning .. &amp;quot;[[&amp;quot; .. link .. &amp;quot;|&amp;quot; .. do_pluralize(linktext) .. &amp;quot;]]&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		if word_ends_in_consonant_plus_y(link) then&lt;br /&gt;
			return beginning .. &amp;quot;[[&amp;quot; .. link .. &amp;quot;|&amp;quot; .. link:gsub(&amp;quot;y$&amp;quot;, &amp;quot;ies&amp;quot;) .. &amp;quot;]]&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		return beginning .. &amp;quot;[[&amp;quot; .. link .. &amp;quot;]]&amp;quot; .. (word_takes_es_plural(link) and &amp;quot;es&amp;quot; or &amp;quot;s&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	return do_pluralize(text)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function export.singularize(text)&lt;br /&gt;
	if type(text) == &amp;quot;table&amp;quot; then&lt;br /&gt;
		-- allow calling from a template&lt;br /&gt;
		text = text.args[1]&lt;br /&gt;
	end&lt;br /&gt;
	-- Singularize a word in a smart fashion, according to normal English rules.&lt;br /&gt;
	-- Works analogously to pluralize().&lt;br /&gt;
	-- NOTE: This doesn&amp;#039;t always work as well as pluralize(). Beware. It will&lt;br /&gt;
	-- mishandle cases like &amp;quot;passes&amp;quot; -&amp;gt; &amp;quot;passe&amp;quot;, &amp;quot;eyries&amp;quot; -&amp;gt; &amp;quot;eyry&amp;quot;.&lt;br /&gt;
	-- 1. If word ends in -ies, replace -ies with -y.&lt;br /&gt;
	-- 2. If the word ends in -xes, -shes, -ches, remove -es. [Does not affect&lt;br /&gt;
	--    -ses, cf. &amp;quot;houses&amp;quot;, &amp;quot;impasses&amp;quot;.]&lt;br /&gt;
	-- 3. Otherwise, remove -s.&lt;br /&gt;
	-- This handles links correctly:&lt;br /&gt;
	-- 1. If a piped link, change the second part appropriately. Collapse the&lt;br /&gt;
	--    link to a simple link if both parts end up the same.&lt;br /&gt;
	-- 2. If a non-piped link, singularize the link.&lt;br /&gt;
	-- 3. A link like &amp;quot;[[parish]]es&amp;quot; will be handled correctly because the&lt;br /&gt;
	--    code that checks for -shes etc. allows ] characters between the&lt;br /&gt;
	--    &amp;#039;sh&amp;#039; etc. and final -es.&lt;br /&gt;
	local function do_singularize(text)&lt;br /&gt;
		local sing = text:match(&amp;quot;^(.-)ies$&amp;quot;)&lt;br /&gt;
		if sing then&lt;br /&gt;
			return sing .. &amp;quot;y&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
		-- Handle cases like &amp;quot;[[parish]]es&amp;quot;&lt;br /&gt;
		local sing = text:match(&amp;quot;^(.-[sc]h%]*)es$&amp;quot;)&lt;br /&gt;
		if sing then&lt;br /&gt;
			return sing&lt;br /&gt;
		end&lt;br /&gt;
		-- Handle cases like &amp;quot;[[box]]es&amp;quot;&lt;br /&gt;
		local sing = text:match(&amp;quot;^(.-x%]*)es$&amp;quot;)&lt;br /&gt;
		if sing then&lt;br /&gt;
			return sing&lt;br /&gt;
		end&lt;br /&gt;
		local sing = text:match(&amp;quot;^(.-)s$&amp;quot;)&lt;br /&gt;
		if sing then&lt;br /&gt;
			return sing&lt;br /&gt;
		end&lt;br /&gt;
		return text&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local function collapse_link(link, linktext)&lt;br /&gt;
		if link == linktext then&lt;br /&gt;
			return &amp;quot;[[&amp;quot; .. link .. &amp;quot;]]&amp;quot;&lt;br /&gt;
		else&lt;br /&gt;
			return &amp;quot;[[&amp;quot; .. link .. &amp;quot;|&amp;quot; .. linktext .. &amp;quot;]]&amp;quot;&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Check for a link. This pattern matches both piped and unpiped links.&lt;br /&gt;
	-- If the link is not piped, the second capture (linktext) will be empty.&lt;br /&gt;
	local beginning, link, linktext = export.match(text, &amp;quot;^(.*)%[%[([^|%]]+)%|?(.-)%]%]$&amp;quot;)&lt;br /&gt;
	if link then&lt;br /&gt;
		if linktext ~= &amp;quot;&amp;quot; then&lt;br /&gt;
			return beginning .. collapse_link(link, do_singularize(linktext))&lt;br /&gt;
		end&lt;br /&gt;
		return beginning .. &amp;quot;[[&amp;quot; .. do_singularize(link) .. &amp;quot;]]&amp;quot;&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return do_singularize(text)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function export.add_indefinite_article(text, uppercase)&lt;br /&gt;
	local is_vowel = false&lt;br /&gt;
	-- If there&amp;#039;s a link at the beginning, examine the first letter of the&lt;br /&gt;
	-- link text. This pattern matches both piped and unpiped links.&lt;br /&gt;
	-- If the link is not piped, the second capture (linktext) will be empty.&lt;br /&gt;
	local link, linktext, remainder = export.match(text, &amp;quot;^%[%[([^|%]]+)%|?(.-)%]%](.*)$&amp;quot;)&lt;br /&gt;
	if link then&lt;br /&gt;
		is_vowel = export.find(linktext ~= &amp;quot;&amp;quot; and linktext or link, &amp;quot;^[AEIOUaeiou]&amp;quot;)&lt;br /&gt;
	else&lt;br /&gt;
		is_vowel = export.find(text, &amp;quot;^[AEIOUaeiou]&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	return (is_vowel and (uppercase and &amp;quot;An &amp;quot; or &amp;quot;an &amp;quot;) or (uppercase and &amp;quot;A &amp;quot; or &amp;quot;a &amp;quot;)) .. text&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Convert risky characters to HTML entities, which minimizes interference once returned (e.g. for &amp;quot;sms:a&amp;quot;, &amp;quot;&amp;lt;!-- --&amp;gt;&amp;quot; etc.).&lt;br /&gt;
function export.escape_risky_characters(text)&lt;br /&gt;
	-- Spacing characters in isolation generally need to be escaped in order to be properly processed by the MediaWiki software.&lt;br /&gt;
	if not mw.ustring.match(text, &amp;quot;%S&amp;quot;) then&lt;br /&gt;
		return mw.text.encode(text, &amp;quot;%s&amp;quot;)&lt;br /&gt;
	else&lt;br /&gt;
		return mw.text.encode(text, &amp;quot;!#%%&amp;amp;*+/:;&amp;lt;=&amp;gt;?@[\\%]_{|}&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return export&lt;/div&gt;</summary>
		<author><name>bob&gt;Djpwikiadmin</name></author>
	</entry>
</feed>