Module:EFloras
require('strict')
 
 local p = {}
 
 local data -- Load [[Module:eFloras/data]] if needed and assign to this variable.
 
 local function getResource(floraID)
 	data = data or mw.loadData(&quot;Module:eFloras/data&quot;)
 	return data.resources[floraID] or data.resources[tonumber(floraID)]
 end
 
 function p.resource(frame)
 	local floraID = string.match(frame.args[1], &quot;%d+&quot;)
 	if floraID == nil then
 		return &quot;&lt;span style=\&quot;color: red;\&quot;&gt;Please provide a resource number (&lt;code&gt;flora_id&lt;/code&gt;). See the list of supported resource numbers at [[Module:eFloras/doc]]&lt;/span&gt;&quot;
 	else
 		local familyToVolume = getResource(floraID)
 		if familyToVolume == nil then
 			return &quot;&lt;span style=\&quot;color: red;\&quot;&gt;The resource number (&lt;code&gt;flora_id&lt;/code&gt;) &lt;code&gt;&quot; .. floraID .. &quot;&lt;/code&gt; is not recognized. See the list of supported resource numbers at [[Module:eFloras/doc]]&lt;/span&gt;[[Category:Pages using eFloras template with unsupported parameter values]]&quot;
 		else
 			return familyToVolume
 		end
 	end
 end
 
 function p._volumeName(floraID, volume, family)
 	floraID = tonumber(floraID)
 	
 	if not floraID then -- floraID is not a number.
 		return
 	end
 	
 	data = data or mw.loadData(&quot;Module:eFloras/data&quot;)
 	
 	if not volume then
 		local familyToVolume = data.volumeTable[floraID]
 		if not familyToVolume then
 			return
 		end
 		
 		volume = tonumber(familyToVolume[family])
 	
 		if not volume then
 			return
 		end
 	end
 	
 	local floraVolumeNames = data.volumeNames and data.volumeNames[floraID]
 	if floraVolumeNames and volume then
 		return floraVolumeNames[volume]
 	end
 end
 
 function p.volumeName(frame)
 	if not (frame.args[1] and (frame.args[2] or frame.args[3] or frame.args.family)) then
 		return
 	end
 	
 	local floraID = string.match(frame.args[1], &quot;%d+&quot;)
 	local volume = tonumber(frame.args[2])
 	local family = frame.args[3] or frame.args.family
 	
 	if not (floraID and (volume or family)) then
 		return
 	end
 	
 	return p._volumeName(floraID, volume, family)
 end
 
 function p._volumeDate(floraID, volume, family)
 	floraID = tonumber(floraID)
 	
 	if not floraID then -- floraID is not a number.
 		return
 	end
 	
 	data = data or mw.loadData(&quot;Module:eFloras/data&quot;)
 	
 	if not volume then
 		local familyToVolume = data.volumeTable[floraID]
 		if not familyToVolume then
 			return
 		end
 		
 		volume = tonumber(familyToVolume[family])
 	
 		if not volume then
 			return
 		end
 	end
 	
 	local floraVolumeDates = data.volumeDates and data.volumeDates[floraID]
 	if floraVolumeDates then
 		if volume and floraVolumeDates[volume] then
 			return floraVolumeDates[volume]
 		else
 			return floraVolumeDates.default
 		end
 	end
 end
 
 function p.volumeDate(frame)
 	if not (frame.args[1] and (frame.args[2] or frame.args[3] or frame.args.family)) then
 		return
 	end
 	
 	local floraID = string.match(frame.args[1], &quot;%d+&quot;)
 	local volume = tonumber(frame.args[2])
 	local family = frame.args[3] or frame.args.family
 	
 	if not (floraID and (volume or family)) then
 		return
 	end
 	
 	return p._volumeDate(floraID, volume, family)
 end
 
 function p.volume(frame)
 	local floraID = string.match(frame.args[1], &quot;%d+&quot;)
 	local family = frame.args[2] or frame.args.family
 	data = data or mw.loadData(&quot;Module:eFloras/data&quot;)
 	local familyToVolume = data.volumeTable[floraID] or data.volumeTable[tonumber(floraID)]
 	if familyToVolume == nil then
 		return &quot;&quot;
 	else
 		local volume = familyToVolume[family]
 		if volume == &quot;error&quot; then
 			return &quot;19&amp;ndash;21 [[Category:Pages using eFloras template with unsupported parameter values]]&quot;
 		elseif volume == nil then
 			return &quot;&quot;
 		else
 			return volume
 		end
 	end
 end
 
 -- Italicize if name requires it.
 function p.italicize(name)
 	local orig = name
 	name = string.gsub(name, &quot;^%s*(.-)%s*$&quot;, &quot;%1&quot;)
 	
 	local count
 	name, count = string.gsub(name, &quot;\'\'\'?&quot;, &quot;&quot;)
 	
 	if count &gt; 0 then
 		-- A tracking method used on Wiktionary: [[wikt:Module:debug]].
 		-- To see the results:
 		-- [[Special:WhatLinksHere/Template:tracking/eFloras/italics or bolding]]
 		local frame = mw.getCurrentFrame()
 		pcall(frame.expandTemplate, frame, { title = 'tracking/eFloras/italics or bolding' })
 		mw.log(&quot;Italics in input to the italicize function in Module:eFloras:&quot;, orig)
 	end
 	
 	local rank
 	local lowerName = name:lower()
 	if name == &quot;&quot; or name == nil then
 		return
 	elseif string.find(name, &quot;^%u%l+ae$&quot;) then
 		if string.find(name, &quot;eae$&quot;) then
 			if string.find(name, &quot;aceae$&quot;) then
 				rank = &quot;family&quot;
 			elseif string.find(name, &quot;oideae$&quot;) then
 				rank = &quot;subfamily&quot;
 			else
 				rank = &quot;tribe&quot;
 			end
 		elseif string.find(name, &quot;inae$&quot;) then
 			rank = &quot;subtribe&quot;
 		end
 	elseif string.find(lowerName, &quot;subsp.&quot;, 1, true) then
 		rank = &quot;subspecies&quot;
 	elseif string.find(lowerName, &quot;subg.&quot;, 1, true) then
 		rank = &quot;subgenus&quot;
 	elseif string.find(lowerName, &quot;var.&quot;, 1, true) then
 		rank = &quot;variety&quot;
 	elseif string.find(lowerName, &quot;sect.&quot;, 1, true) then
 		rank = &quot;section&quot;
 	elseif string.find(name, &quot;^%a+%s[%a-]+$&quot;) or string.find(name, &quot;^%a+%s×%s[%a-]+$&quot;) then
 		rank = &quot;species&quot;
 	elseif string.find(name, &quot;^%u%a+$&quot;) -- No one-letter genera, probably.
 			and not string.find(name, &quot;.%u&quot;) then -- Uppercase letters can only appear at beginning of taxonomic name.
 		rank = &quot;genus&quot;
 	end
 	
 	if not rank then
 		mw.log(&quot;Module:eFloras could not determine a taxonomic rank for the input that it received: &quot; .. name)
 		return orig
 	end
 	
 	if rank == &quot;genus&quot; or rank == &quot;subgenus&quot; or rank == &quot;species&quot;
 			or rank == &quot;subspecies&quot; or rank == &quot;variety&quot; or rank == &quot;section&quot; then
 		
 		name = &quot;&lt;i&gt;&quot; .. name .. &quot;&lt;/i&gt;&quot;
 		local hybrid = &quot;×&quot;
 		
 		if name:find(&quot;.&quot;, 1, true) then
 			local abbreviations = {
 				[&quot;subsp.&quot;] = true, [&quot;ssp.&quot;] = true, [&quot;var.&quot;] = true, [&quot;f.&quot;] = true,
 				[&quot;sect.&quot;] = true, [&quot;subsect.&quot;] = true, [&quot;subg.&quot;] = true,
 			}
 			
 			local unrecognized
 			name = name:gsub(
 				&quot;%s+(%S+%.)%s+&quot;,
 				function (abbreviation)
 					mw.log(name, abbreviation, abbreviation:lower(), abbreviations[abbreviation:lower()])
 					if abbreviations[abbreviation:lower()] then
 						return &quot;&lt;/i&gt; &quot; .. abbreviation .. &quot; &lt;i&gt;&quot;
 					else
 						unrecognized = unrecognized or {}
 						table.insert(unrecognized, abbreviation)
 					end
 				end)
 			
 			if unrecognized then
 				mw.log(string.format(&quot;The abbreviation%s %s %s not recognized.&quot;,
 					unrecognized[2] and &quot;s&quot; or &quot;&quot;,
 					table.concat(
 						unrecognized,
 						&quot;, &quot;),
 					unrecognized[2] and &quot;are&quot; or &quot;is&quot;))
 				return orig
 			end
 		end
 		
 		name = name:gsub(&quot;%s+&quot; .. hybrid .. &quot;%s+&quot;, &quot;&lt;/i&gt; &quot; .. hybrid .. &quot; &lt;i&gt;&quot;)
 	end -- Else do not modify name.
 	
 	return name
 end
 
 function p.name(frame)
 	local name = frame.args[1]
 	return p.italicize(name)
 end 
 
 p.get_volume = p.volume