<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://linguifex.com/w/index.php?action=history&amp;feed=atom&amp;title=Module%3Aparse_utilities</id>
	<title>Module:parse utilities - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://linguifex.com/w/index.php?action=history&amp;feed=atom&amp;title=Module%3Aparse_utilities"/>
	<link rel="alternate" type="text/html" href="https://linguifex.com/w/index.php?title=Module:parse_utilities&amp;action=history"/>
	<updated>2026-04-06T02:52:48Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.43.6</generator>
	<entry>
		<id>https://linguifex.com/w/index.php?title=Module:parse_utilities&amp;diff=475301&amp;oldid=prev</id>
		<title>Sware at 21:42, 4 November 2025</title>
		<link rel="alternate" type="text/html" href="https://linguifex.com/w/index.php?title=Module:parse_utilities&amp;diff=475301&amp;oldid=prev"/>
		<updated>2025-11-04T21:42:09Z</updated>

		<summary type="html">&lt;p&gt;&lt;/p&gt;
&lt;a href=&quot;https://linguifex.com/w/index.php?title=Module:parse_utilities&amp;amp;diff=475301&amp;amp;oldid=424188&quot;&gt;Show changes&lt;/a&gt;</summary>
		<author><name>Sware</name></author>
	</entry>
	<entry>
		<id>https://linguifex.com/w/index.php?title=Module:parse_utilities&amp;diff=424188&amp;oldid=prev</id>
		<title>Sware at 19:55, 11 January 2025</title>
		<link rel="alternate" type="text/html" href="https://linguifex.com/w/index.php?title=Module:parse_utilities&amp;diff=424188&amp;oldid=prev"/>
		<updated>2025-01-11T19:55:36Z</updated>

		<summary type="html">&lt;p&gt;&lt;/p&gt;
&lt;a href=&quot;https://linguifex.com/w/index.php?title=Module:parse_utilities&amp;amp;diff=424188&amp;amp;oldid=302587&quot;&gt;Show changes&lt;/a&gt;</summary>
		<author><name>Sware</name></author>
	</entry>
	<entry>
		<id>https://linguifex.com/w/index.php?title=Module:parse_utilities&amp;diff=302587&amp;oldid=prev</id>
		<title>Sware: Created page with &quot;local export = {}  local m_string_utilities = require(&quot;Module:string utilities&quot;)  local rsplit = mw.text.split local u = mw.ustring.char local rsubn = mw.ustring.gsub  -- version of rsubn() that discards all but the first return value local function rsub(term, foo, bar) 	local retval = rsubn(term, foo, bar) 	return retval end   --[=[ In order to understand the following parsing code, you need to understand how inflected text specs work. They are intended to work with inf...&quot;</title>
		<link rel="alternate" type="text/html" href="https://linguifex.com/w/index.php?title=Module:parse_utilities&amp;diff=302587&amp;oldid=prev"/>
		<updated>2023-05-03T14:42:34Z</updated>

		<summary type="html">&lt;p&gt;Created page with &amp;quot;local export = {}  local m_string_utilities = require(&amp;quot;Module:string utilities&amp;quot;)  local rsplit = mw.text.split local u = mw.ustring.char local rsubn = mw.ustring.gsub  -- version of rsubn() that discards all but the first return value local function rsub(term, foo, bar) 	local retval = rsubn(term, foo, bar) 	return retval end   --[=[ In order to understand the following parsing code, you need to understand how inflected text specs work. They are intended to work with inf...&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;local export = {}&lt;br /&gt;
&lt;br /&gt;
local m_string_utilities = require(&amp;quot;Module:string utilities&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
local rsplit = mw.text.split&lt;br /&gt;
local u = mw.ustring.char&lt;br /&gt;
local rsubn = mw.ustring.gsub&lt;br /&gt;
&lt;br /&gt;
-- version of rsubn() that discards all but the first return value&lt;br /&gt;
local function rsub(term, foo, bar)&lt;br /&gt;
	local retval = rsubn(term, foo, bar)&lt;br /&gt;
	return retval&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[=[&lt;br /&gt;
In order to understand the following parsing code, you need to understand how inflected text specs work. They are&lt;br /&gt;
intended to work with inflected text where individual words to be inflected may be followed by inflection specs in&lt;br /&gt;
angle brackets. The format of the text inside of the angle brackets is up to the individual language and part-of-speech&lt;br /&gt;
specific implementation. A real-world example is as follows: &amp;quot;[[медичний|меди́чна]]&amp;lt;+&amp;gt; [[сестра́]]&amp;lt;*,*#.pr&amp;gt;&amp;quot;. This is the&lt;br /&gt;
inflection of a multiword expression &amp;quot;меди́чна сестра́&amp;quot;, which means &amp;quot;nurse&amp;quot; in Ukrainian (literally &amp;quot;medical sister&amp;quot;),&lt;br /&gt;
consisting of two words: the adjective меди́чна (&amp;quot;medical&amp;quot; in the feminine singular) and the noun сестра́ (&amp;quot;sister&amp;quot;). The&lt;br /&gt;
specs in angle brackets follow each word to be inflected; for example, &amp;lt;+&amp;gt; means that the preceding word should be&lt;br /&gt;
declined as an adjective.&lt;br /&gt;
&lt;br /&gt;
The code below works in terms of balanced expressions, which are bounded by delimiters such as &amp;lt; &amp;gt; or [ ]. The&lt;br /&gt;
intention is to allow separators such as spaces to be embedded inside of delimiters; such embedded separators will not&lt;br /&gt;
be parsed as separators. For example, Ukrainian noun specs allow footnotes in brackets to be inserted inside of angle&lt;br /&gt;
brackets; something like &amp;quot;меди́чна&amp;lt;+&amp;gt; сестра́&amp;lt;pr.[this is a footnote]&amp;gt;&amp;quot; is legal, as is&lt;br /&gt;
&amp;quot;[[медичний|меди́чна]]&amp;lt;+&amp;gt; [[сестра́]]&amp;lt;pr.[this is an &amp;lt;i&amp;gt;italicized footnote&amp;lt;/i&amp;gt;]&amp;gt;&amp;quot;, and the parsing code should not be&lt;br /&gt;
confused by the embedded brackets, spaces or angle brackets.&lt;br /&gt;
&lt;br /&gt;
The parsing is done by two functions, which work in close concert: parse_balanced_segment_run() and&lt;br /&gt;
split_alternating_runs(). To illustrate, consider the following:&lt;br /&gt;
&lt;br /&gt;
parse_balanced_segment_run(&amp;quot;foo&amp;lt;M.proper noun&amp;gt; bar&amp;lt;F&amp;gt;&amp;quot;, &amp;quot;&amp;lt;&amp;quot;, &amp;quot;&amp;gt;&amp;quot;) =&lt;br /&gt;
  {&amp;quot;foo&amp;quot;, &amp;quot;&amp;lt;M.proper noun&amp;gt;&amp;quot;, &amp;quot; bar&amp;quot;, &amp;quot;&amp;lt;F&amp;gt;&amp;quot;, &amp;quot;&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
then&lt;br /&gt;
&lt;br /&gt;
split_alternating_runs({&amp;quot;foo&amp;quot;, &amp;quot;&amp;lt;M.proper noun&amp;gt;&amp;quot;, &amp;quot; bar&amp;quot;, &amp;quot;&amp;lt;F&amp;gt;&amp;quot;, &amp;quot;&amp;quot;}, &amp;quot; &amp;quot;) =&lt;br /&gt;
  {{&amp;quot;foo&amp;quot;, &amp;quot;&amp;lt;M.proper noun&amp;gt;&amp;quot;, &amp;quot;&amp;quot;}, {&amp;quot;bar&amp;quot;, &amp;quot;&amp;lt;F&amp;gt;&amp;quot;, &amp;quot;&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
Here, we start out with a typical inflected text spec &amp;quot;foo&amp;lt;M.proper noun&amp;gt; bar&amp;lt;F&amp;gt;&amp;quot;, call parse_balanced_segment_run() on&lt;br /&gt;
it, and call split_alternating_runs() on the result. The output of parse_balanced_segment_run() is a list where&lt;br /&gt;
even-numbered segments are bounded by the bracket-like characters passed into the function, and odd-numbered segments&lt;br /&gt;
consist of the surrounding text. split_alternating_runs() is called on this, and splits *only* the odd-numbered&lt;br /&gt;
segments, grouping all segments between the specified character. Note that the inner lists output by&lt;br /&gt;
split_alternating_runs() are themselves in the same format as the output of parse_balanced_segment_run(), with&lt;br /&gt;
bracket-bounded text in the even-numbered segments. Hence, such lists can be passed again to split_alternating_runs().&lt;br /&gt;
]=]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Parse a string containing matched instances of parens, brackets or the like. Return a list of strings, alternating&lt;br /&gt;
-- between textual runs not containing the open/close characters and runs beginning and ending with the open/close&lt;br /&gt;
-- characters. For example,&lt;br /&gt;
--&lt;br /&gt;
-- parse_balanced_segment_run(&amp;quot;foo(x(1)), bar(2)&amp;quot;, &amp;quot;(&amp;quot;, &amp;quot;)&amp;quot;) = {&amp;quot;foo&amp;quot;, &amp;quot;(x(1))&amp;quot;, &amp;quot;, bar&amp;quot;, &amp;quot;(2)&amp;quot;, &amp;quot;&amp;quot;}.&lt;br /&gt;
function export.parse_balanced_segment_run(segment_run, open, close)&lt;br /&gt;
	return m_string_utilities.capturing_split(segment_run, &amp;quot;(%b&amp;quot; .. open .. close .. &amp;quot;)&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- The following is an equivalent, older implementation that does not use %b (written before I was aware of %b).&lt;br /&gt;
--[=[&lt;br /&gt;
function export.parse_balanced_segment_run(segment_run, open, close)&lt;br /&gt;
	local break_on_open_close = m_string_utilities.capturing_split(segment_run, &amp;quot;([%&amp;quot; .. open .. &amp;quot;%&amp;quot; .. close .. &amp;quot;])&amp;quot;)&lt;br /&gt;
	local text_and_specs = {}&lt;br /&gt;
	local level = 0&lt;br /&gt;
	local seg_group = {}&lt;br /&gt;
	for i, seg in ipairs(break_on_open_close) do&lt;br /&gt;
		if i % 2 == 0 then&lt;br /&gt;
			if seg == open then&lt;br /&gt;
				table.insert(seg_group, seg)&lt;br /&gt;
				level = level + 1&lt;br /&gt;
			else&lt;br /&gt;
				assert(seg == close)&lt;br /&gt;
				table.insert(seg_group, seg)&lt;br /&gt;
				level = level - 1&lt;br /&gt;
				if level &amp;lt; 0 then&lt;br /&gt;
					error(&amp;quot;Unmatched &amp;quot; .. close .. &amp;quot; sign: &amp;#039;&amp;quot; .. segment_run .. &amp;quot;&amp;#039;&amp;quot;)&lt;br /&gt;
				elseif level == 0 then&lt;br /&gt;
					table.insert(text_and_specs, table.concat(seg_group))&lt;br /&gt;
					seg_group = {}&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		elseif level &amp;gt; 0 then&lt;br /&gt;
			table.insert(seg_group, seg)&lt;br /&gt;
		else&lt;br /&gt;
			table.insert(text_and_specs, seg)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if level &amp;gt; 0 then&lt;br /&gt;
		error(&amp;quot;Unmatched &amp;quot; .. open .. &amp;quot; sign: &amp;#039;&amp;quot; .. segment_run .. &amp;quot;&amp;#039;&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	return text_and_specs&lt;br /&gt;
end&lt;br /&gt;
]=]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Like parse_balanced_segment_run() but accepts multiple sets of delimiters. For example,&lt;br /&gt;
--&lt;br /&gt;
-- parse_multi_delimiter_balanced_segment_run(&amp;quot;foo[bar(baz[bat])], quux&amp;lt;glorp&amp;gt;&amp;quot;, {{&amp;quot;[&amp;quot;, &amp;quot;]&amp;quot;}, {&amp;quot;(&amp;quot;, &amp;quot;)&amp;quot;}, {&amp;quot;&amp;lt;&amp;quot;, &amp;quot;&amp;gt;&amp;quot;}}) =&lt;br /&gt;
--		{&amp;quot;foo&amp;quot;, &amp;quot;[bar(baz[bat])]&amp;quot;, &amp;quot;, quux&amp;quot;, &amp;quot;&amp;lt;glorp&amp;gt;&amp;quot;, &amp;quot;&amp;quot;}.&lt;br /&gt;
function export.parse_multi_delimiter_balanced_segment_run(segment_run, delimiter_pairs)&lt;br /&gt;
	local open_to_close_map = {}&lt;br /&gt;
	local open_close_items = {}&lt;br /&gt;
	for _, open_close in ipairs(delimiter_pairs) do&lt;br /&gt;
		local open, close = unpack(open_close)&lt;br /&gt;
		open_to_close_map[open] = close&lt;br /&gt;
		table.insert(open_close_items, &amp;quot;%&amp;quot; .. open)&lt;br /&gt;
		table.insert(open_close_items, &amp;quot;%&amp;quot; .. close)&lt;br /&gt;
	end&lt;br /&gt;
	local open_close_pattern = &amp;quot;([&amp;quot; .. table.concat(open_close_items) .. &amp;quot;])&amp;quot;&lt;br /&gt;
	local break_on_open_close = m_string_utilities.capturing_split(segment_run, open_close_pattern)&lt;br /&gt;
	local text_and_specs = {}&lt;br /&gt;
	local level = 0&lt;br /&gt;
	local seg_group = {}&lt;br /&gt;
	local open_at_level_zero&lt;br /&gt;
	for i, seg in ipairs(break_on_open_close) do&lt;br /&gt;
		if i % 2 == 0 then&lt;br /&gt;
			table.insert(seg_group, seg)&lt;br /&gt;
			if level == 0 then&lt;br /&gt;
				if not open_to_close_map[seg] then&lt;br /&gt;
					error(&amp;quot;Unmatched &amp;quot; .. seg .. &amp;quot; sign: &amp;#039;&amp;quot; .. segment_run .. &amp;quot;&amp;#039;&amp;quot;)&lt;br /&gt;
				end&lt;br /&gt;
				assert(open_at_level_zero == nil)&lt;br /&gt;
				open_at_level_zero = seg&lt;br /&gt;
				level = level + 1&lt;br /&gt;
			elseif seg == open_at_level_zero then&lt;br /&gt;
				level = level + 1&lt;br /&gt;
			elseif seg == open_to_close_map[open_at_level_zero] then&lt;br /&gt;
				level = level - 1&lt;br /&gt;
				assert(level &amp;gt;= 0)&lt;br /&gt;
				if level == 0 then&lt;br /&gt;
					table.insert(text_and_specs, table.concat(seg_group))&lt;br /&gt;
					seg_group = {}&lt;br /&gt;
					open_at_level_zero = nil&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		elseif level &amp;gt; 0 then&lt;br /&gt;
			table.insert(seg_group, seg)&lt;br /&gt;
		else&lt;br /&gt;
			table.insert(text_and_specs, seg)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if level &amp;gt; 0 then&lt;br /&gt;
		error(&amp;quot;Unmatched &amp;quot; .. open_at_level_zero .. &amp;quot; sign: &amp;#039;&amp;quot; .. segment_run .. &amp;quot;&amp;#039;&amp;quot;)&lt;br /&gt;
	end&lt;br /&gt;
	return text_and_specs&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[=[&lt;br /&gt;
Split a list of alternating textual runs of the format returned by `parse_balanced_segment_run` on `splitchar`. This&lt;br /&gt;
only splits the odd-numbered textual runs (the portions between the balanced open/close characters).  The return value&lt;br /&gt;
is a list of lists, where each list contains an odd number of elements, where the even-numbered elements of the sublists&lt;br /&gt;
are the original balanced textual run portions. For example, if we do&lt;br /&gt;
&lt;br /&gt;
parse_balanced_segment_run(&amp;quot;foo&amp;lt;M.proper noun&amp;gt; bar&amp;lt;F&amp;gt;&amp;quot;, &amp;quot;&amp;lt;&amp;quot;, &amp;quot;&amp;gt;&amp;quot;) =&lt;br /&gt;
  {&amp;quot;foo&amp;quot;, &amp;quot;&amp;lt;M.proper noun&amp;gt;&amp;quot;, &amp;quot; bar&amp;quot;, &amp;quot;&amp;lt;F&amp;gt;&amp;quot;, &amp;quot;&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
then&lt;br /&gt;
&lt;br /&gt;
split_alternating_runs({&amp;quot;foo&amp;quot;, &amp;quot;&amp;lt;M.proper noun&amp;gt;&amp;quot;, &amp;quot; bar&amp;quot;, &amp;quot;&amp;lt;F&amp;gt;&amp;quot;, &amp;quot;&amp;quot;}, &amp;quot; &amp;quot;) =&lt;br /&gt;
  {{&amp;quot;foo&amp;quot;, &amp;quot;&amp;lt;M.proper noun&amp;gt;&amp;quot;, &amp;quot;&amp;quot;}, {&amp;quot;bar&amp;quot;, &amp;quot;&amp;lt;F&amp;gt;&amp;quot;, &amp;quot;&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
Note that we did not touch the text &amp;quot;&amp;lt;M.proper noun&amp;gt;&amp;quot; even though it contains a space in it, because it is an&lt;br /&gt;
even-numbered element of the input list. This is intentional and allows for embedded separators inside of&lt;br /&gt;
brackets/parens/etc. Note also that the inner lists in the return value are of the same form as the input list (i.e.&lt;br /&gt;
they consist of alternating textual runs where the even-numbered segments are balanced runs), and can in turn be passed&lt;br /&gt;
to split_alternating_runs().&lt;br /&gt;
&lt;br /&gt;
If `preserve_splitchar` is passed in, the split character is included in the output, as follows:&lt;br /&gt;
&lt;br /&gt;
split_alternating_runs({&amp;quot;foo&amp;quot;, &amp;quot;&amp;lt;M.proper noun&amp;gt;&amp;quot;, &amp;quot; bar&amp;quot;, &amp;quot;&amp;lt;F&amp;gt;&amp;quot;, &amp;quot;&amp;quot;}, &amp;quot; &amp;quot;, true) =&lt;br /&gt;
  {{&amp;quot;foo&amp;quot;, &amp;quot;&amp;lt;M.proper noun&amp;gt;&amp;quot;, &amp;quot;&amp;quot;}, {&amp;quot; &amp;quot;}, {&amp;quot;bar&amp;quot;, &amp;quot;&amp;lt;F&amp;gt;&amp;quot;, &amp;quot;&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
Consider what happens if the original string has multiple spaces between brackets, and multiple sets of brackets&lt;br /&gt;
without spaces between them.&lt;br /&gt;
&lt;br /&gt;
parse_balanced_segment_run(&amp;quot;foo[dated][low colloquial] baz-bat quux xyzzy[archaic]&amp;quot;, &amp;quot;[&amp;quot;, &amp;quot;]&amp;quot;) =&lt;br /&gt;
  {&amp;quot;foo&amp;quot;, &amp;quot;[dated]&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;[low colloquial]&amp;quot;, &amp;quot; baz-bat quux xyzzy&amp;quot;, &amp;quot;[archaic]&amp;quot;, &amp;quot;&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
then&lt;br /&gt;
&lt;br /&gt;
split_alternating_runs({&amp;quot;foo&amp;quot;, &amp;quot;[dated]&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;[low colloquial]&amp;quot;, &amp;quot; baz-bat quux xyzzy&amp;quot;, &amp;quot;[archaic]&amp;quot;, &amp;quot;&amp;quot;}, &amp;quot;[ %-]&amp;quot;) =&lt;br /&gt;
  {{&amp;quot;foo&amp;quot;, &amp;quot;[dated]&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;[low colloquial]&amp;quot;, &amp;quot;&amp;quot;}, {&amp;quot;baz&amp;quot;}, {&amp;quot;bat&amp;quot;}, {&amp;quot;quux&amp;quot;}, {&amp;quot;xyzzy&amp;quot;, &amp;quot;[archaic]&amp;quot;, &amp;quot;&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
If `preserve_splitchar` is passed in, the split character is included in the output,&lt;br /&gt;
as follows:&lt;br /&gt;
&lt;br /&gt;
split_alternating_runs({&amp;quot;foo&amp;quot;, &amp;quot;[dated]&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;[low colloquial]&amp;quot;, &amp;quot; baz bat quux xyzzy&amp;quot;, &amp;quot;[archaic]&amp;quot;, &amp;quot;&amp;quot;}, &amp;quot;[ %-]&amp;quot;, true) =&lt;br /&gt;
  {{&amp;quot;foo&amp;quot;, &amp;quot;[dated]&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;[low colloquial]&amp;quot;, &amp;quot;&amp;quot;}, {&amp;quot; &amp;quot;}, {&amp;quot;baz&amp;quot;}, {&amp;quot;-&amp;quot;}, {&amp;quot;bat&amp;quot;}, {&amp;quot; &amp;quot;}, {&amp;quot;quux&amp;quot;}, {&amp;quot; &amp;quot;}, {&amp;quot;xyzzy&amp;quot;, &amp;quot;[archaic]&amp;quot;, &amp;quot;&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
As can be seen, the even-numbered elements in the outer list are one-element lists consisting of the separator text.&lt;br /&gt;
]=]&lt;br /&gt;
function export.split_alternating_runs(segment_runs, splitchar, preserve_splitchar)&lt;br /&gt;
	local grouped_runs = {}&lt;br /&gt;
	local run = {}&lt;br /&gt;
	for i, seg in ipairs(segment_runs) do&lt;br /&gt;
		if i % 2 == 0 then&lt;br /&gt;
			table.insert(run, seg)&lt;br /&gt;
		else&lt;br /&gt;
			local parts =&lt;br /&gt;
				preserve_splitchar and m_string_utilities.capturing_split(seg, &amp;quot;(&amp;quot; .. splitchar .. &amp;quot;)&amp;quot;) or&lt;br /&gt;
				rsplit(seg, splitchar)&lt;br /&gt;
			table.insert(run, parts[1])&lt;br /&gt;
			for j=2,#parts do&lt;br /&gt;
				table.insert(grouped_runs, run)&lt;br /&gt;
				run = {parts[j]}&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if #run &amp;gt; 0 then&lt;br /&gt;
		table.insert(grouped_runs, run)&lt;br /&gt;
	end&lt;br /&gt;
	return grouped_runs&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function export.strip_spaces(text)&lt;br /&gt;
	return rsub(text, &amp;quot;^%s*(.-)%s*$&amp;quot;, &amp;quot;%1&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Apply an arbitrary function `frob` to the &amp;quot;raw-text&amp;quot; segments in a split run set (the output of&lt;br /&gt;
-- split_alternating_runs()). We leave alone stuff within balanced delimiters (footnotes, inflection specs and the&lt;br /&gt;
-- like), as well as splitchars themselves if present. `preserve_splitchar` indicates whether splitchars are present&lt;br /&gt;
-- in the split run set. `frob` is a function of one argument (the string to frob) and should return one argument (the&lt;br /&gt;
-- frobbed string). We operate by only frobbing odd-numbered segments, and only in odd-numbered runs if&lt;br /&gt;
-- preserve_splitchar is given.&lt;br /&gt;
function export.frob_raw_text_alternating_runs(split_run_set, frob, preserve_splitchar)&lt;br /&gt;
	for i, run in ipairs(split_run_set) do&lt;br /&gt;
		if not preserve_splitchar or i % 2 == 1 then&lt;br /&gt;
			for j, segment in ipairs(run) do&lt;br /&gt;
				if j % 2 == 1 then&lt;br /&gt;
					run[j] = frob(segment)&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Like split_alternating_runs() but applies an arbitrary function `frob` to &amp;quot;raw-text&amp;quot; segments in the result (i.e.&lt;br /&gt;
-- not stuff within balanced delimiters such as footnotes and inflection specs, and not splitchars if present). `frob`&lt;br /&gt;
-- is a function of one argument (the string to frob) and should return one argument (the frobbed string).&lt;br /&gt;
function export.split_alternating_runs_and_frob_raw_text(run, splitchar, frob, preserve_splitchar)&lt;br /&gt;
	local split_runs = export.split_alternating_runs(run, splitchar, preserve_splitchar)&lt;br /&gt;
	export.frob_raw_text_alternating_runs(split_runs, frob, preserve_splitchar)&lt;br /&gt;
	return split_runs&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Split the non-modifier parts of an alternating run (after parse_balanced_segment_run() is called) on a Lua pattern,&lt;br /&gt;
-- but not on certain sequences involving characters in that pattern (e.g. comma+whitespace). `splitchar` is the pattern&lt;br /&gt;
-- to split on; `preserve_splitchar` indicates whether to preserve the delimiter and is the same as in&lt;br /&gt;
-- split_alternating_runs(). `escape_fun` is called beforehand on each run of raw text and should return two values:&lt;br /&gt;
-- the escaped run and whether unescaping is needed. If any call to `escape_fun` indicates that unescaping is needed,&lt;br /&gt;
-- `unescape_fun` will be called on each run of raw text after splitting on `splitchar`. The return value of this&lt;br /&gt;
-- function is as in split_alternating_runs().&lt;br /&gt;
function export.split_alternating_runs_escaping(run, splitchar, preserve_splitchar, escape_fun, unescape_fun)&lt;br /&gt;
	-- First replace comma with a temporary character in comma+whitespace sequences.&lt;br /&gt;
	local need_unescape = false&lt;br /&gt;
	for i, seg in ipairs(run) do&lt;br /&gt;
		if i % 2 == 1 then&lt;br /&gt;
			local this_need_unescape&lt;br /&gt;
			run[i], this_need_unescape = escape_fun(run[i])&lt;br /&gt;
			need_unescape = need_unescape or this_need_unescape&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if need_unescape then&lt;br /&gt;
		return export.split_alternating_runs_and_frob_raw_text(run, splitchar, unescape_fun, preserve_splitchar)&lt;br /&gt;
	else&lt;br /&gt;
		return export.split_alternating_runs(run, splitchar, preserve_splitchar)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Replace comma with a temporary char in comma + whitespace.&lt;br /&gt;
function export.escape_comma_whitespace(run, tempcomma)&lt;br /&gt;
	tempcomma = tempcomma or u(0xFFF0)&lt;br /&gt;
&lt;br /&gt;
	if run:find(&amp;quot;,%s&amp;quot;) then&lt;br /&gt;
		run = run:gsub(&amp;quot;,(%s)&amp;quot;, tempcomma .. &amp;quot;%1&amp;quot;) -- assign to temp to discard second return value&lt;br /&gt;
		return run, true&lt;br /&gt;
	else&lt;br /&gt;
		return run, false&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Undo the replacement of comma with a temporary char.&lt;br /&gt;
function export.unescape_comma_whitespace(run, tempcomma)&lt;br /&gt;
	tempcomma = tempcomma or u(0xFFF0)&lt;br /&gt;
&lt;br /&gt;
	run = run:gsub(tempcomma, &amp;quot;,&amp;quot;) -- assign to temp to discard second return value&lt;br /&gt;
	return run&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Split the non-modifier parts of an alternating run (after parse_balanced_segment_run() is called) on comma, but not&lt;br /&gt;
-- on comma+whitespace. See `split_on_comma()` above for more information and the meaning of `tempcomma`.&lt;br /&gt;
function export.split_alternating_runs_on_comma(run, tempcomma)&lt;br /&gt;
	tempcomma = tempcomma or u(0xFFF0)&lt;br /&gt;
&lt;br /&gt;
	-- Replace comma with a temporary char in comma + whitespace.&lt;br /&gt;
	local function escape_comma_whitespace(seg)&lt;br /&gt;
		return export.escape_comma_whitespace(seg, tempcomma)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Undo replacement of comma with a temporary char in comma + whitespace.&lt;br /&gt;
	local function unescape_comma_whitespace(seg)&lt;br /&gt;
		return export.unescape_comma_whitespace(seg, tempcomma)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return export.split_alternating_runs_escaping(run, &amp;quot;,&amp;quot;, false, escape_comma_whitespace, unescape_comma_whitespace)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Split text on a Lua pattern, but not on certain sequences involving characters in that pattern (e.g.&lt;br /&gt;
-- comma+whitespace). `splitchar` is the pattern to split on; `preserve_splitchar` indicates whether to preserve the&lt;br /&gt;
-- delimiter between split segments. `escape_fun` is called beforehand on the text and should return two values: the&lt;br /&gt;
-- escaped run and whether unescaping is needed. If the call to `escape_fun` indicates that unescaping is needed,&lt;br /&gt;
-- `unescape_fun` will be called on each run of text after splitting on `splitchar`. The return value of this a list&lt;br /&gt;
-- of runs, interspersed with delimiters if `preserve_splitchar` is specified.&lt;br /&gt;
function export.split_escaping(text, splitchar, preserve_splitchar, escape_fun, unescape_fun)&lt;br /&gt;
	-- First escape sequences we don&amp;#039;t want to count for splitting.&lt;br /&gt;
	local need_unescape&lt;br /&gt;
	text, need_unescape = escape_fun(text)&lt;br /&gt;
&lt;br /&gt;
	local parts =&lt;br /&gt;
		preserve_splitchar and m_string_utilities.capturing_split(text, &amp;quot;(&amp;quot; .. splitchar .. &amp;quot;)&amp;quot;) or&lt;br /&gt;
		rsplit(text, splitchar)&lt;br /&gt;
	if need_unescape then&lt;br /&gt;
		for i = 1, #parts, (preserve_splitchar and 2 or 1) do&lt;br /&gt;
			parts[i] = unescape_fun(parts[i])&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return parts&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Split text on comma, but not on comma+whitespace. This is similar to `mw.text.split(text, &amp;quot;,&amp;quot;)` but will not split&lt;br /&gt;
-- on commas directly followed by whitespace, to handle embedded commas in terms (which are almost always followed by&lt;br /&gt;
-- a space). `tempcomma` is the Unicode character to temporarily use when doing the splitting; normally U+FFF0, but&lt;br /&gt;
-- you can specify a different character if you use U+FFF0 for some internal purpose.&lt;br /&gt;
function export.split_on_comma(text, tempcomma)&lt;br /&gt;
	tempcomma = tempcomma or u(0xFFF0)&lt;br /&gt;
&lt;br /&gt;
	-- Replace comma with a temporary char in comma + whitespace.&lt;br /&gt;
	local function escape_comma_whitespace(run)&lt;br /&gt;
		return export.escape_comma_whitespace(run, tempcomma)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Undo replacement of comma with a temporary char in comma + whitespace.&lt;br /&gt;
	local function unescape_comma_whitespace(run)&lt;br /&gt;
		return export.unescape_comma_whitespace(run, tempcomma)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return export.split_escaping(text, &amp;quot;,&amp;quot;, false, escape_comma_whitespace, unescape_comma_whitespace)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Ensure that Wikicode (bracketed links, HTML, bold/italics, etc.) displays literally in error messages by inserting&lt;br /&gt;
-- a Unicode word-joiner symbol after all characters that may trigger Wikicode interpr. Replacing with equivalent&lt;br /&gt;
-- HTML escapes doesn&amp;#039;t work because they are displayed literally. I could not get this to work using&lt;br /&gt;
-- &amp;lt;nowiki&amp;gt;...&amp;lt;/nowiki&amp;gt; (those tags display literally) and using using {{#tag:nowiki|...}} (same thing).&lt;br /&gt;
-- FIXME: This is a massive hack; there must be a better way.&lt;br /&gt;
function export.escape_wikicode(term)&lt;br /&gt;
	term = term:gsub(&amp;quot;([%[&amp;lt;&amp;#039;])&amp;quot;, &amp;quot;%1&amp;quot; .. u(0x2060))&lt;br /&gt;
	return term&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Parse a term that may have a language code preceding it (e.g. &amp;#039;la:minūtia&amp;#039; or &amp;#039;grc:[[σκῶρ|σκατός]]&amp;#039;). Return&lt;br /&gt;
-- two arguments, the term minus the language code and the language object corresponding to the language code.&lt;br /&gt;
-- Etymology-only languages are allowed. This function also correctly handles Wikipedia prefixes (e.g. &amp;#039;w:Abatemarco&amp;#039;&lt;br /&gt;
-- or &amp;#039;w:it:Colle Val d&amp;#039;Elsa&amp;#039;) and converts them into two-part links, with the display form not including the Wikipedia&lt;br /&gt;
-- prefix. `parse_err` should be a function of one or two arguments to display an error (the second argument is the&lt;br /&gt;
-- number of stack frames to ignore when calling error(); if you declare your error function with only one argument,&lt;br /&gt;
-- things will still work fine).&lt;br /&gt;
function export.parse_term_with_lang(term, parse_err)&lt;br /&gt;
	-- Parse off an initial language code (e.g. &amp;#039;la:minūtia&amp;#039; or &amp;#039;grc:[[σκῶρ|σκατός]]&amp;#039;). Also handle Wikipedia prefixes&lt;br /&gt;
	-- (&amp;#039;w:Abatemarco&amp;#039; or &amp;#039;w:it:Colle Val d&amp;#039;Elsa&amp;#039;).&lt;br /&gt;
	local termlang, actual_term = term:match(&amp;quot;^([A-Za-z0-9._-]+):(.*)$&amp;quot;)&lt;br /&gt;
	if termlang == &amp;quot;w&amp;quot; then&lt;br /&gt;
		local foreign_wikipedia, foreign_term = actual_term:match(&amp;quot;^([A-Za-z0-9._-]+):(.*)$&amp;quot;)&lt;br /&gt;
		if foreign_wikipedia then&lt;br /&gt;
			termlang = termlang .. &amp;quot;:&amp;quot; .. foreign_wikipedia&lt;br /&gt;
			actual_term = foreign_term&lt;br /&gt;
		end&lt;br /&gt;
		if actual_term:find(&amp;quot;[%[%]]&amp;quot;) then&lt;br /&gt;
			parse_err(&amp;quot;Cannot have brackets following a Wikipedia (w:...) link; place the Wikipedia link inside the brackets&amp;quot;)&lt;br /&gt;
		end&lt;br /&gt;
		term = (&amp;quot;[[%s:%s|%s]]&amp;quot;):format(termlang, actual_term, actual_term)&lt;br /&gt;
		termlang = nil&lt;br /&gt;
	elseif termlang then&lt;br /&gt;
		termlang = require(&amp;quot;Module:languages&amp;quot;).getByCode(termlang, parse_err, &amp;quot;allow etym&amp;quot;)&lt;br /&gt;
		term = actual_term&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return term, termlang&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[=[&lt;br /&gt;
Parse a term that may have inline modifiers attached (e.g. &amp;#039;rifiuti&amp;lt;q:plural-only&amp;gt;&amp;#039; or&lt;br /&gt;
&amp;#039;rinfusa&amp;lt;t:bulk cargo&amp;gt;&amp;lt;lit:resupplying&amp;gt;&amp;lt;qq:more common in the plural {{m|it|rinfuse}}&amp;gt;&amp;#039;).&lt;br /&gt;
  * `arg` is the term to parse.&lt;br /&gt;
  * `props` is an object holding further properties controlling how to parse the term:&lt;br /&gt;
	 * `paramname` is the name of the parameter where `arg` comes from, or nil if this isn&amp;#039;t available (it is used only&lt;br /&gt;
	   in error messages).&lt;br /&gt;
	 * `param_mods` is a table describing the allowed inline modifiers (see below).&lt;br /&gt;
	 * `generate_obj` is a function of one or two arguments that should parse the argument minus the inline modifiers&lt;br /&gt;
	   and return a corresponding parsed  object (into which the inline modifiers will be rewritten). If declared with&lt;br /&gt;
	   one argument, that will be the raw value to parse; if declared with two arguments, the second argument will be&lt;br /&gt;
	   the `parse_err` function (see below).&lt;br /&gt;
	 * `parse_err` is an optional function of one argument (an error message) and should display the error message,&lt;br /&gt;
	   along with any desired contextual text (e.g. the argument name and value that triggered the error). If omitted,&lt;br /&gt;
	   a default function will be generated which displays the error along with the original value of `arg` (passed&lt;br /&gt;
	   through escape_wikicode() above to ensure that Wikicode (such as links) is displayed literally).&lt;br /&gt;
	 * `splitchar` is a Lua pattern. If specified, `arg` can consist of multiple delimiter-separated terms, each of&lt;br /&gt;
	   which may be followed by inline modifiers, and the return value will be a list of parsed objects instead of a&lt;br /&gt;
	   single object. Note that splitting on delimiters will not happen in certain protected sequences (by default&lt;br /&gt;
	   comma+whitespace; see below). The algorithm to split on delimiters is sensitive to inline modifier syntax and&lt;br /&gt;
	   will not be confused by delimiters inside of inline modifiers, which do not trigger splitting (whether or not&lt;br /&gt;
	   contained within protected sequences).&lt;br /&gt;
	 * `preserve_splitchar`, if specified, causes the actual delimiter matched by `splitchar` to be returned in the&lt;br /&gt;
	   parsed object describing the element that comes after the delimiter. The delimiter is stored in a key whose&lt;br /&gt;
	   name is controlled by `separator_key`, which defaults to &amp;quot;separator&amp;quot;.&lt;br /&gt;
	 * `separator_key` controls the key into which the actual delimiter is written when `preserve_splitchar` is used.&lt;br /&gt;
	   See above.&lt;br /&gt;
	 * `escape_fun` and `unescape_fun` are as in split_escaping() and split_alternating_runs_escaping() above and&lt;br /&gt;
	   control the protected sequences that won&amp;#039;t be split. By default, `escape_comma_whitespace` and&lt;br /&gt;
	   `unescape_comma_whitespace` are used, so that comma+whitespace sequences won&amp;#039;t be split.&lt;br /&gt;
&lt;br /&gt;
`param_mods` is a table describing allowed modifiers. The keys of the table are modifier prefixes and the values are&lt;br /&gt;
tables describing how to parse and store the associated modifier values. Here is a typical example:&lt;br /&gt;
&lt;br /&gt;
local param_mods = {&lt;br /&gt;
	t = {&lt;br /&gt;
		item_dest = &amp;quot;gloss&amp;quot;,&lt;br /&gt;
	},&lt;br /&gt;
	gloss = {},&lt;br /&gt;
	pos = {},&lt;br /&gt;
	alt = {},&lt;br /&gt;
	lit = {},&lt;br /&gt;
	id = {},&lt;br /&gt;
	g = {&lt;br /&gt;
		item_dest = &amp;quot;genders&amp;quot;,&lt;br /&gt;
		convert = function(arg)&lt;br /&gt;
			return rsplit(arg, &amp;quot;,&amp;quot;)&lt;br /&gt;
		end,&lt;br /&gt;
	},&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
In the table values:&lt;br /&gt;
* `item_dest` specifies the destination key to store the object into (if not the same as the modifier key itself).&lt;br /&gt;
* `convert` is a function of one or two arguments (the modifier value and optionally the `parse_err` function as passed&lt;br /&gt;
  in or generated), and should parse and convert the value into the appropriate object. If omitted, the string value is&lt;br /&gt;
  stored unchanged.&lt;br /&gt;
* `store` describes how to store the converted modifier value into the parsed object. If omitted, the converted value&lt;br /&gt;
  is simply written into the parsed object under the appropriate key (but an error is generated if the key already has&lt;br /&gt;
  a value). If `store` == &amp;quot;insert&amp;quot;, the converted value is appended to the key&amp;#039;s value using table.insert();&lt;br /&gt;
  if the key has no value, it is first converted to an empty list. `store` == &amp;quot;insertIfNot&amp;quot; is similar but appends the&lt;br /&gt;
  value using insertIfNot() in [[Module:table]]. If `store == &amp;quot;insert-flattened&amp;quot;, the converted value is assumed to be&lt;br /&gt;
  a list and the objects are appended one-by-one into the key&amp;#039;s existing value using table.insert(). `store` ==&lt;br /&gt;
  &amp;quot;insertIfNot-flattened&amp;quot; is similar but appends using insertIfNot() in [[Module:table]]. WARNING: When using&lt;br /&gt;
  &amp;quot;insert-flattened&amp;quot; and &amp;quot;insertIfNot-flattened&amp;quot;, if there is no existing value for the key, the converted value is&lt;br /&gt;
  just stored directly. This means that future appends will side-effect that value, so make sure that the return&lt;br /&gt;
  value of the conversion function for this key generates a fresh list each time.&lt;br /&gt;
]=]&lt;br /&gt;
function export.parse_inline_modifiers(arg, props)&lt;br /&gt;
	local function get_valid_prefixes()&lt;br /&gt;
		local valid_prefixes = {}&lt;br /&gt;
		for param_mod, _ in pairs(props.param_mods) do&lt;br /&gt;
			table.insert(valid_prefixes, param_mod)&lt;br /&gt;
		end&lt;br /&gt;
		table.sort(valid_prefixes)&lt;br /&gt;
		return valid_prefixes&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local function get_arg_gloss()&lt;br /&gt;
		if props.paramname then&lt;br /&gt;
			return (&amp;quot;%s=%s&amp;quot;):format(props.paramname, arg)&lt;br /&gt;
		else&lt;br /&gt;
			return arg&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local parse_err = props.parse_err&lt;br /&gt;
	if not parse_err then&lt;br /&gt;
		parse_err = function(msg, stack_frames_to_ignore)&lt;br /&gt;
			error(export.escape_wikicode((&amp;quot;%s: %s&amp;quot;):format(msg, get_arg_gloss())), stack_frames_to_ignore)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local segments = export.parse_balanced_segment_run(arg, &amp;quot;&amp;lt;&amp;quot;, &amp;quot;&amp;gt;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
	local function parse_group(group)&lt;br /&gt;
		local dest_obj = props.generate_obj(group[1], parse_err)&lt;br /&gt;
		for k = 2, #group - 1, 2 do&lt;br /&gt;
			if group[k + 1] ~= &amp;quot;&amp;quot; then&lt;br /&gt;
				parse_err(&amp;quot;Extraneous text &amp;#039;&amp;quot; .. group[k + 1] .. &amp;quot;&amp;#039; after modifier&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
			local modtext = group[k]:match(&amp;quot;^&amp;lt;(.*)&amp;gt;$&amp;quot;)&lt;br /&gt;
			if not modtext then&lt;br /&gt;
				parse_err(&amp;quot;Internal error: Modifier &amp;#039;&amp;quot; .. group[k] .. &amp;quot;&amp;#039; isn&amp;#039;t surrounded by angle brackets&amp;quot;)&lt;br /&gt;
			end&lt;br /&gt;
			local prefix, val = modtext:match(&amp;quot;^([a-zA-Z0-9+_-]+):(.*)$&amp;quot;)&lt;br /&gt;
			if not prefix then&lt;br /&gt;
				local valid_prefixes = get_valid_prefixes()&lt;br /&gt;
				for i, valid_prefix in ipairs(valid_prefixes) do&lt;br /&gt;
					valid_prefixes[i] = &amp;quot;&amp;#039;&amp;quot; .. valid_prefix .. &amp;quot;:&amp;#039;&amp;quot;&lt;br /&gt;
				end&lt;br /&gt;
				parse_err(&amp;quot;Modifier &amp;quot; .. group[k] .. &amp;quot; lacks a prefix, should begin with one of &amp;quot; ..&lt;br /&gt;
					require(&amp;quot;Module:table&amp;quot;).serialCommaJoin(valid_prefixes, {dontTag = true}))&lt;br /&gt;
			end&lt;br /&gt;
			if props.param_mods[prefix] then&lt;br /&gt;
				local function prefix_parse_err(msg, stack_frames_to_ignore)&lt;br /&gt;
					error(export.escape_wikicode((&amp;quot;%s: modifier prefix &amp;#039;%s&amp;#039; in %s&amp;quot;):format(msg, prefix, get_arg_gloss())), stack_frames_to_ignore)&lt;br /&gt;
				end&lt;br /&gt;
				local key = props.param_mods[prefix].item_dest or prefix&lt;br /&gt;
				local convert = props.param_mods[prefix].convert&lt;br /&gt;
				local converted&lt;br /&gt;
				if convert then&lt;br /&gt;
					converted = convert(val, prefix_parse_err)&lt;br /&gt;
				else&lt;br /&gt;
					converted = val&lt;br /&gt;
				end&lt;br /&gt;
				local store = props.param_mods[prefix].store&lt;br /&gt;
				if not store then&lt;br /&gt;
					if dest_obj[key] then&lt;br /&gt;
						parse_err(&amp;quot;Modifier &amp;#039;&amp;quot; .. prefix .. &amp;quot;&amp;#039; occurs twice, second occurrence &amp;quot; .. group[k])&lt;br /&gt;
					end&lt;br /&gt;
					dest_obj[key] = converted&lt;br /&gt;
				elseif store == &amp;quot;insert&amp;quot; then&lt;br /&gt;
					if not dest_obj[key] then&lt;br /&gt;
						dest_obj[key] = {converted}&lt;br /&gt;
					else&lt;br /&gt;
						table.insert(dest_obj[key], converted)&lt;br /&gt;
					end&lt;br /&gt;
				elseif store == &amp;quot;insertIfNot&amp;quot; then&lt;br /&gt;
					if not dest_obj[key] then&lt;br /&gt;
						dest_obj[key] = {converted}&lt;br /&gt;
					else&lt;br /&gt;
						require(&amp;quot;Module:table&amp;quot;).insertIfNot(dest_obj[key], converted)&lt;br /&gt;
					end&lt;br /&gt;
				elseif store == &amp;quot;insert-flattened&amp;quot; then&lt;br /&gt;
					if not dest_obj[key] then&lt;br /&gt;
						dest_obj[key] = obj&lt;br /&gt;
					else&lt;br /&gt;
						for _, obj in ipairs(converted) do&lt;br /&gt;
							table.insert(dest_obj[key], obj)&lt;br /&gt;
						end&lt;br /&gt;
					end&lt;br /&gt;
				elseif store == &amp;quot;insertIfNot-flattened&amp;quot; then&lt;br /&gt;
					if not dest_obj[key] then&lt;br /&gt;
						dest_obj[key] = obj&lt;br /&gt;
					else&lt;br /&gt;
						for _, obj in ipairs(converted) do&lt;br /&gt;
							require(&amp;quot;Module:table&amp;quot;).insertIfNot(dest_obj[key], obj)&lt;br /&gt;
						end&lt;br /&gt;
					end&lt;br /&gt;
				else&lt;br /&gt;
					store(dest_obj, key, converted, prefix_parse_err)&lt;br /&gt;
				end&lt;br /&gt;
			else&lt;br /&gt;
				local valid_prefixes = get_valid_prefixes()&lt;br /&gt;
				for i, valid_prefix in ipairs(valid_prefixes) do&lt;br /&gt;
					valid_prefixes[i] = &amp;quot;&amp;#039;&amp;quot; .. valid_prefix .. &amp;quot;&amp;#039;&amp;quot;&lt;br /&gt;
				end&lt;br /&gt;
				parse_err(&amp;quot;Unrecognized prefix &amp;#039;&amp;quot; .. prefix .. &amp;quot;&amp;#039; in modifier &amp;quot; .. group[k]&lt;br /&gt;
					.. &amp;quot;, should be &amp;quot; .. require(&amp;quot;Module:table&amp;quot;).serialCommaJoin(valid_prefixes, {dontTag = true}))&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		return dest_obj&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if not props.splitchar then&lt;br /&gt;
		return parse_group(segments)&lt;br /&gt;
	else&lt;br /&gt;
		local retval = {}&lt;br /&gt;
		local separated_groups = export.split_alternating_runs_escaping(segments, props.splitchar,&lt;br /&gt;
			props.preserve_splitchar, props.escape_fun or export.escape_comma_whitespace,&lt;br /&gt;
			props.unescape_fun or export.unescape_comma_whitespace)&lt;br /&gt;
		for j = 1, #separated_groups, (props.preserve_splitchar and 2 or 1) do&lt;br /&gt;
			local parsed = parse_group(separated_groups[j])&lt;br /&gt;
			if props.preserve_splitchar and j &amp;gt; 1 then&lt;br /&gt;
				parsed[props.separator_key or &amp;quot;separator&amp;quot;] = separated_groups[j - 1][1]&lt;br /&gt;
			end&lt;br /&gt;
			table.insert(retval, parsed)&lt;br /&gt;
		end&lt;br /&gt;
		return retval&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
return export&lt;/div&gt;</summary>
		<author><name>Sware</name></author>
	</entry>
</feed>