Module:TaxonItalics
--[[=========================================================================
 Italicize a taxon name appropriately by invoking italicizeTaxonName.
 The algorithm used is:
 * If the name has italic markup at the start or the end, do nothing.
 * Else
   * Remove (internal) italic markup.
   * If the name is made up of four words and the third word is a
     botanical connecting term, de-italicize the connecting term and add italic
     markup to the outside of the name.
   * Else if the name is made up of three words and the second word is a
     botanical connecting term or a variant of &quot;cf.&quot;, de-italicize the
     connecting term and add italic markup to the outside of the name.
   * Else just add italic markup to the outside of the name.
  The module also:
  * Ensures that the hybrid symbol, ×, and parentheses are not italicized, as
    well as any string inside parentheses if dab is true.
  * Has an option to abbreviate all parts of taxon names other than the last
    to the first letter (e.g. &quot;Pinus sylvestris var. sylvestris&quot; becomes
    &quot;P. s. var. sylvestris&quot;).
  * Has an option to wikilink the italicized name to the input name.
 =============================================================================]]
 
 local p = {}
 local l = {} -- used to store purely local functions
 
 --connecting terms in three part names (e.g. Pinus sylvestris var. sylvestris)
 local cTerms3 = {
 	--subsp.
     subspecies = &quot;subsp.&quot;,
     [&quot;subsp.&quot;] = &quot;subsp.&quot;,
     subsp = &quot;subsp.&quot;,
     [&quot;ssp.&quot;] = &quot;subsp.&quot;,
     ssp = &quot;subsp.&quot;,
     --var.
     varietas = &quot;var.&quot;,
     [&quot;var.&quot;] = &quot;var.&quot;,
     var = &quot;var.&quot;,
     --subvar.
     subvarietas = &quot;subvar.&quot;,
     [&quot;subvar.&quot;] = &quot;subvar.&quot;,
     subvar = &quot;subvar.&quot;,
     --f.
     forma = &quot;f.&quot;,
     [&quot;f.&quot;] = &quot;f.&quot;,
     f = &quot;f.&quot;,
     --subf.
     subforma = &quot;subf.&quot;,
     [&quot;subf.&quot;] = &quot;subf.&quot;,
     subf = &quot;subf.&quot;
     }
 --connecting terms in two part names (e.g. Pinus sect. Pinus)
 local cTerms2 = {
 	--subg.
     subgenus = &quot;subg.&quot;,
     [&quot;subgen.&quot;] = &quot;subg.&quot;,
     [&quot;subg.&quot;] = &quot;subg.&quot;,
     subg = &quot;subg.&quot;,
     --supersect.
     supersection = &quot;supersect.&quot;,
     [&quot;supersect.&quot;] = &quot;supersect.&quot;,
     supersect = &quot;supersect.&quot;,
     --sect.
     section = &quot;sect.&quot;,
     [&quot;sect.&quot;] = &quot;sect.&quot;,
     sect = &quot;sect.&quot;,
     --subsect.
     subsection = &quot;subsect.&quot;,
     [&quot;subsect.&quot;] = &quot;subsect.&quot;,
     subsect = &quot;subsect.&quot;,
     --ser.
     series = &quot;ser.&quot;,
     [&quot;ser.&quot;] = &quot;ser.&quot;,
     ser = &quot;ser.&quot;,
     --subser.
     subseries = &quot;subser.&quot;,
     [&quot;subser.&quot;] = &quot;subser.&quot;,
     subser = &quot;subser.&quot;,
     --cf.
     cf = &quot;cf.&quot;,
     [&quot;cf.&quot;] = &quot;cf.&quot;,
     [&quot;c.f.&quot;] = &quot;cf.&quot;
     }
 
 --[[=========================================================================
 Main function to italicize a taxon name appropriately. For the purpose of the
 parameters, see p.italicizeTaxonName().
 =============================================================================]]
 function p.main(frame)
     local name = frame.args[1] or ''
     local linked = frame.args['linked'] == 'yes'
     local abbreviated = frame.args['abbreviated'] == 'yes'
     local dab = frame.args['dab'] == 'yes'
     return p.italicizeTaxonName(name, linked, abbreviated, dab)
 end
 
 --[[=========================================================================
 Utility local function to abbreviate an input string to its first character
 followed by &quot;.&quot;.
 Both &quot;×&quot; and an HTML entity at the start of the string are skipped over in
 determining first character, as is an opening parenthesis and an opening &quot;,
 which cause a matching closing character to be included.
 =============================================================================]]
 function l.abbreviate(str)
 	local result = &quot;&quot;
 	local hasParentheses = false
 	local isQuoted = false
 	if mw.ustring.len(str) &lt; 2 then
 		--single character strings are left unchanged
 		result = str
 	else
 		--skip over an opening parenthesis that could be present at the start of the string
 		if mw.ustring.sub(str,1,1) == &quot;(&quot; then
 			hasParentheses = true
 			result = &quot;(&quot; 
 			str = mw.ustring.sub(str,2,mw.ustring.len(str))
 		elseif mw.ustring.sub(str,1,1) == '&quot;' then
 			isQuoted = true
 			result = '&quot;'
 			str = mw.ustring.sub(str,2,mw.ustring.len(str))
 		end
 		--skip over a hybrid symbol that could be present at the start of the string
 		if mw.ustring.sub(str,1,1) == &quot;×&quot; then
 			result = &quot;×&quot; 
 			str = mw.ustring.sub(str,2,mw.ustring.len(str))
 		end
 		--skip over an HTML entity that could be present at the start of the string
 		if mw.ustring.sub(str,1,1) == &quot;&amp;&quot; then
 			local i,dummy = mw.ustring.find(str,&quot;;&quot;,2,plain)
 			result = result .. mw.ustring.sub(str,1,i)
 			str = mw.ustring.sub(str,i+1,mw.ustring.len(str))
 		end
 		--if there's anything left, reduce it to its first character plus &quot;.&quot;,
 		--adding the closing parenthesis or quote if required
 		if str ~= &quot;&quot; then 
 			result = result .. mw.ustring.sub(str,1,1) .. &quot;.&quot;
 			if hasParentheses then result = result .. &quot;)&quot;
 			elseif isQuoted then result = result .. '&quot;'
 			end
 		end
 	end
 	return result
 end
 
 --[[=========================================================================
 The function which does the italicization. Parameters:
  name (string) – the taxon name to be processed
  linked (boolean) – should a wikilink be generated?
  abbreviated (boolean) – should the first parts of the taxon name be
    reduced to capital letters?
  dab (boolean) – should any parenthesized part be treated as a disambiguation
    term and left unitalicized?
 =============================================================================]]
 function p.italicizeTaxonName(name, linked, abbreviated, dab)
     name = mw.text.trim(name)
     -- if the name begins with '[', then assume formatting is present
     if  mw.ustring.sub(name,1,1) == '[' then return name end
     -- otherwise begin by replacing any use of the HTML italic tags
     -- by Wikimedia markup; replace any entity alternatives to the hybrid symbol
     -- by the symbol itself; prevent the hybrid symbol being treated as
     -- a 'word' by converting a following space to the HTML entity
     local italMarker = &quot;''&quot;
     name = string.gsub(mw.text.trim(name), &quot;&lt;/?i&gt;&quot;, italMarker)
     name = string.gsub(string.gsub(name, &quot;&amp;#215;&quot;, &quot;×&quot;), &quot;&amp;times;&quot;, &quot;×&quot;)
     name = string.gsub(name, &quot;&lt;/?span.-&gt;&quot;, &quot;&quot;) -- remove any span markup
     name = string.gsub(name, &quot;× &quot;, &quot;×&amp;#32;&quot;)
     -- now italicize and abbreviate if required
     local result = name
     if name ~= '' then
         if string.sub(name,1,2) == italMarker or string.sub(name,-2) == italMarker then
             -- do nothing if the name already has italic markers at the start or end
         else
             name = string.gsub(name, italMarker, &quot;&quot;) -- first remove any internal italics
             local words = mw.text.split(name, &quot; &quot;, true)
             if #words == 4 and cTerms3[words[3]] then
                 -- the third word of a four word name is a connecting term
                 -- ensure the connecting term isn't italicized
                 words[3] = '&lt;span style=&quot;font-style:normal;&quot;&gt;' .. cTerms3[words[3]] .. '&lt;/span&gt;'
                 if abbreviated then
                 	words[1] = l.abbreviate(words[1])
                     words[2] = l.abbreviate(words[2])
             	end
                 result = words[1] .. &quot; &quot; .. words[2] .. &quot; &quot; .. words[3] .. &quot; &quot; .. words[4]
             elseif #words == 3 and cTerms2[words[2]] then
                 -- the second word of a three word name is a connecting term
                 -- ensure the connecting term isn't italicized
                 words[2] = '&lt;span style=&quot;font-style:normal;&quot;&gt;' .. cTerms2[words[2]] .. '&lt;/span&gt;'
                 if abbreviated then
                 	words[1] = l.abbreviate(words[1])
                 end
                 result = words[1] .. &quot; &quot; .. words[2] .. &quot; &quot; .. words[3]
             else
                 -- not a name as above; only deal with abbreviation
                 if abbreviated then
                 	if #words &gt; 1 then
                 		result = l.abbreviate(words[1])
                 		for i = 2, #words-1, 1 do
                 			result = result .. &quot; &quot; .. l.abbreviate(words[i])
                 		end
                 		result = result .. &quot; &quot; .. words[#words]
                 	end
                 else
                 	result = name
                 end
             end
             -- deal with any hybrid symbol as it should not be italicized
             result = string.gsub(result, &quot;×&quot;, '&lt;span style=&quot;font-style:normal;&quot;&gt;×&lt;/span&gt;')
              -- deal with any parentheses as they should not be italicized
             if dab then
             	result = string.gsub(string.gsub(result,&quot;%(&quot;,'&lt;span style=&quot;font-style:normal;&quot;&gt;('),&quot;%)&quot;,')&lt;/span&gt;')
             else
             	result = string.gsub(string.gsub(result,&quot;%(&quot;,'&lt;span style=&quot;font-style:normal;&quot;&gt;(&lt;/span&gt;'),&quot;%)&quot;,'&lt;span style=&quot;font-style:normal;&quot;&gt;)&lt;/span&gt;')
             end
             -- any question marks surrounded by spans can have the spans joined
             result = string.gsub(result,'&lt;/span&gt;%?&lt;span style=&quot;font%-style:normal;&quot;&gt;','?')
         	-- add outside markup
             if linked then
                 if result ~= name then
                     result = &quot;[[&quot; .. name .. &quot;|&quot; .. italMarker .. result .. italMarker .. &quot;]]&quot;
                 else
                     result = italMarker .. &quot;[[&quot; .. name .. &quot;]]&quot; .. italMarker
                 end
             else
                 result = italMarker .. result .. italMarker
             end
         end
     end
     return result
 end
 
 --[[=========================================================================
 Utility function used by other modules to check if a connecting term is
 present in a name. The value of name is assumed to be plain text.
 =============================================================================]]
 function p.hasCT(frame)
     return p.hasConnectingTerm(frame.args[1] or '')
 end
     
 function p.hasConnectingTerm(name)
 	local words = mw.text.split(name, &quot; &quot;, true)
 	if #words == 4 and cTerms3[words[3]] then return true
 	elseif #words == 3 and cTerms2[words[2]] then return true
 	else return false
 	end
 end