Module:Sandbox/Uanfala/glt

From Wikipedia, the free encyclopedia
Jump to navigation Jump to search
Module documentation[view] [edit] [history] [purge]

This is the module for Template:Interlinear and Template:gcl. The function invoked by the first one is p.intelinearise and the one invoked by the latter is p.gcl. See those templates' documentation for usage instructions.

Most of the glossing abbreviations are loaded from the data subpage.

local p = {}
local data = mw.loadData( 'Module:Sandbox/Uanfala/glt/data' )
local gloss_override = {} -- for custom gloss abbreviations
local getArgs = require('Module:Arguments').getArgs
local yesno = require('Module:Yesno')
local lang_data = mw.loadData( 'Module:Lang/data' )

--------------------------
-- Almost-global variables
--------------------------
local glossing_type, displaying_messages, free_translation, msg, buffer

-------------------
-- General settings 
-------------------
local conf = { --settings
    WordSeparator = " \n\r\t", -- Don't replace with %s as this would include non-breaking spaces
    GlossAbbrPattern = "^([Ø0-9A-Z]+)$", -- this isn't a full regex, but a Lua pattern
    GlossAbbrBoundary = "-.,;:<>‹›/\\~\+=%?%s%[%]()%_'",
    GlossExcludeTable = {I = true,}, --strings not be treated as glossing abbreviations
    GlossExcludePattern = '[0-9][0-9]+',
    GlossSmallCapsExclude = "^[AOPS]$", -- glossing abbreviations matching this pattern will not be rendered in small caps
    GlossingType = "label", -- if set to "label" gloss abbreviations are formatted as an <abbr> with the "label" appearing in a tooltip
                        -- if set to "wikilink" the abbreviation is formatted as a wikilink to the relevant wikipedia article
                        -- if set to "none" abbreviations aren't formatted at all
    ErrorCategory = "[[Category:Pages with errors in interlinear text]]",
    AmbiguousGlossCategory = "[[Category:Articles with ambiguous glossing abbreviations]]",
    MessageGlossingError = "Error(s) in interlinear glossing",
    combining_gender_numbers = "[0-9][0-9]?$", --e.g. G4 '4th gender' or CL7 'class 7'
    combining_gender_prefixes = {G = "gender", CL = "class"},
    combining_person = {["1"] = "first person", ["2"] = "second person", ["3"] = "third person"},
    combining_number = {S = "singular", SG = "singular", P = "plural", PL = "plural", D = "dual", DU = "dual", TRI = "trial"},
    combining_gender = {F = "feminine", M = "masculine", N = "neuter"},
    LowerCaseGlosses = {["1sg"] = true, ["2sg"] = true, ["3sg"] = true, ["1du"] = true, ["2du"] = true, ["3du"] = true, ["1pl"] = true, ["2pl"] = true,
    	["3pl"] = true, ["Fsg"] = true, ["Fpl"] = true, ["Msg"] = true, ["Mpl"] = true,}, -- these are the non-all-upper-case strings that will be recognised as glossing abbreviations
    ErrorHelpLocation = "Template:Interlinear",
}

---------------------
-- CSS styles and classes
---------------------
conf.style = { --CSS styles
    WordDiv = "float: left; margin-bottom: 0.3em;",
    WordMargin = "margin-right: 1em;",
    WordP = "margin: 0px;", -- the style for the word <p> elements
    GlossAbbr = "font-variant: small-caps; font-variant-numeric: oldstyle-nums; text-transform: lowercase; ", -- won't be applied to gloss abbreviations containing lower-case characters
    HiddenText = "display: none;",
    EndDiv = "clear: left; display: block;", -- style of the <div> element at the end of the interlinear display
    ErrorMessage = "font-size: inherit",
}
conf.class = { --CSS classes
	Interlinear = "interlinear",
	GlossAbbr  = "gloss-abbr",
    GlossAbbrAmb = "gloss-abbr-ambiguous",
    GlossAbbrError = "gloss-abbr-error",
    ErrorMessage = "error",
}

---------------------
-- Sundry small functions
---------------------
local function normalise(str)
    return mw.ustring.gsub(str,"[" .. conf.WordSeparator .. "]+"," ")
end

local function tidyCss(str)
    str = mw.ustring.gsub(str, '^[\"\']*(.-)[\"\']*$', "%1") -- trims quotation marks
    if mw.ustring.sub(str, -1) ~= ";" then str = str .. ";" end -- appends ";" if missing
    return str
end

local function highlight(text)
	if text then
		return '<span style="color:#C00;font-weight:bold;">' .. text .. '</span>'
	else return "" end
end

local function tone_sup(str)
	return mw.ustring.gsub(str, "([^%p%s0-9])([0-9])", "%1<sup>%2</sup>")
end

local function is_empty(str) -- returns "false" if its argument is a string containing chars other than spaces &c.
    if not str then return true end
    if mw.ustring.find(str, "[^" .. conf.WordSeparator .. "]")
        then return false
    else return true end
end

local function help_link (anchor)
    if anchor then
        return " ([[" .. conf.ErrorHelpLocation .. "#" .. anchor .. "|help]])"
    else return "" end
end

-- the following is part of a trial implementation of automatic transliteration:
local function transliterate (str, lang_from, lang_to,  scheme)
    local lookup = {grc = {module = 'Module:Ancient Greek', funct = "transliterate", } }
    if not lang_from then
        msg:add("error", "Source language for transliteration is not set")
    else
        local t = lookup[lang_from]
        if t then
            local module = require(t.module)
            return module[t.funct](str)
        else msg:add("error", "Can't find transliterator for language '" .. lang_from .. "'")
        end
    end
    return ""
end -- end of trial block

--------------------
-- The following two functions update the glossing settings based on the received
-- template arguments. set_global_glossing_settings() updates the global settings
-- that are valid for all gloss abbreviations. set_glossing_type()
-- returns the glossing type, which can vary between the different lines.
--------------------
local function set_global_glossing_settings(a)
    local style = ""
    if a.style then style = tidyCss(a.style) end
    if a.underline == "no" then
        style = style .. "text-decoration: none;" end
    if a.small_caps == "no" then
        style = style .. "font-variant:normal; text-transform: none;" end
    if style ~= "" then conf.style.GlossAbbr = conf.style.GlossAbbr .. style end
end

local function set_glossing_type(glossing)
    if glossing then
		local GlossingType
        glossing = mw.ustring.lower(mw.text.trim(glossing))
        if mw.ustring.find(glossing, 'link') then
            GlossingType = "wikilink"
        elseif mw.ustring.find(glossing, 'label')
        	or  mw.ustring.find(glossing, 'no link') then
            GlossingType = 'label'
        elseif mw.ustring.find(glossing, 'no abbr') then
            GlossingType = "no abbr"
        elseif yesno(glossing) == false then
            GlossingType = nil
        elseif yesno(glossing) then
            GlossingType = conf.GlossingType
        else
            msg:add('error', 'Glossing type "' .. glossing .. '" not recognised') end
		return GlossingType
    else error("set_glossing_type: 'glossing' is nil or false", 2)
    end
end

local function set_custom_glosses(list)
    local abbs = mw.text.split(list, '[;\n\t]')
    for _,v in pairs(abbs) do
        local gloss = mw.text.split(v, ':')
        local a = mw.text.trim(gloss[1])
        if a and a ~= "" then
            gloss_override[a] = {}
            gloss_override[a].expansion = gloss[2]
            gloss_override[a].wikipage = gloss[3]
        end
    end
end

---------------------
-- The UserMessages object contains and processes error messages and warnings
---------------------
local UserMessages = {errors = {}, warnings = {}, gloss_messages = {}}
function UserMessages:add(msgtype, text, gloss)
    if msgtype == "gloss_message" then
        self.gloss_messages[gloss] = text
    elseif msgtype == "warning" then
        table.insert(self.warnings, text)
    elseif msgtype == "non-repeating error" then
        self.errors.nre = text
    elseif msgtype == "ambiguous gloss" then
        self.if_ambiguous_glosses = true
    elseif msgtype == "error" then
        table.insert(self.errors, text)
    else return error("UserMessages:add(): unknown message type", 2)
    end
end
function UserMessages:print_errors()
    local out = ""
    local namespace = mw.title.getCurrentTitle().namespace
    if next(self.errors) or self.warnings[1] then
        local err_span = mw.html.create("span")
        err_span:attr("style", conf.style.ErrorMessage)
        err_span:addClass(conf.class.ErrorMessage)
    	for _,v in pairs(self.errors) do
    		err_span:wikitext(" " .. v .. ";") end
    	if namespace % 2 == 0 and namespace ~= 2 -- non-talk namespaces, excluding user pages; if modifying please update the description on the category page
            then err_span:wikitext(conf.ErrorCategory)
        end
        out = tostring(err_span)
    	mw.addWarning(conf.MessageGlossingError)
    end
    if self.if_ambiguous_glosses then
    	if namespace == 0 -- article namespace
           then out = out .. conf.AmbiguousGlossCategory -- this category will only track articles
        end
    end
    return out
end
function UserMessages:print_warnings()
    local out = ""
    -- Messages and warnings get displayed only if the page is being viewed in "preview" mode:
    if displaying_messages and (next(self.gloss_messages) or next(self.warnings)) then
        local div = mw.html.create("div")
        div:addClass("messagebox")
        	:css("margin", "inherit")
            :wikitext("<i>This message box is shown only in preview:</i>")
            :newline()
        for _,v in ipairs(self.warnings) do
        	local p = div:tag("p")
        	p:addClass(conf.class.ErrorMessage)
        	p:attr("style", conf.style.ErrorMessage)
        	p:wikitext(v)
        end
        if self.gloss_messages then
        	div:wikitext("<p>  To change any of the following default expansions, see [[Template:Interlinear/doc#Custom abbreviations|the template's documentation]]:</p>")
        	end
        for _,v in pairs(self.gloss_messages) do
            div:wikitext("<p>" .. v .. "</p>")
        end
        out = out .. "\n\n" .. tostring(div)
    end
    return out
end

---------------------
-- gloss_lookup() receives a gloss abbreviation and tries to uncover its meaning.
---------------------
local function gloss_lookup(a, label, wikilink)
    local _label, _wikilink, _lookup = nil, nil, nil
    if gloss_override[a] then _lookup = gloss_override[a]
    elseif data.abbreviations[a] then _lookup = data.abbreviations[a] end
    if _lookup and _lookup.expansion ~= "" then
        _label, _wikilink = _lookup.expansion, _lookup.wikipage
    else
    	local prefix = mw.ustring.sub(a,1,1)
    	local suffix = mw.ustring.sub(a,2)
    	if conf.combining_person[prefix] then -- is it of the form 1PL or 3FS?
    		_label = conf.combining_person[prefix]
		local _suffix = conf.combining_number[suffix] or conf.combining_gender[suffix]
    		if _suffix then
    			_label = _label .. ", " .. _suffix
    		else
    			local suffix1 = mw.ustring.sub(suffix,1,1)
    			local suffix2 = mw.ustring.sub(suffix,2)
    				if conf.combining_gender[suffix1]
    				and  conf.combining_number[suffix2] then
    					_label = _label .. ", " .. conf.combining_gender[suffix1] .. ", " .. conf.combining_number[suffix2]
    				else _label = nil end
    		end
	elseif mw.ustring.match(suffix,conf.combining_gender_numbers) then -- cases like G4 = gender 4
		local _i,_j = mw.ustring.find(a, conf.combining_gender_numbers)
		local _pre = mw.ustring.sub(a, 1, _i - 1)
		local _suff = mw.ustring.sub(a, _i)
		if conf.combining_gender_prefixes[_pre] then
			_label = conf.combining_gender_prefixes[_pre] .. " " .. _suff
		end
	elseif prefix == "N" then -- dealing with cases like NPST = non-past
		local s = gloss_override[suffix] or data.abbreviations[suffix]
        	if s ~= nil and not s.ExcludeNegation then
        	    _label = "non-" .. s.expansion
    	     _wikilink = s.wikipage
    		end
    		s = nil
    	end
    end
    if _label == "" then _label = nil end
    if _wikilink == "" then _wikilink = nil end
    if not label then label = _label end
    if not wikilink then wikilink = _wikilink end
    return label, wikilink
end

---------------------
-- format_gloss() calls gloss_lookup() to find the meaning of a gloss
-- abbreviation, which it then proceeds to format
---------------------
local function format_gloss(gloss, label, wikilink)
	local gloss2 = mw.ustring.gsub(gloss,"<.->","") -- remove any html fluff
    gloss2 = mw.ustring.gsub(gloss2, "%'%'+", "") -- remove wiki bold/italic formatting
    gloss2 = mw.text.trim(mw.ustring.upper(gloss2))
    if not (label or wikilink)
        or (not label and glossing_type == "label")
        or (not wikilink  and glossing_type == "wikilink")
        then
            if glossing_type ~= "no abbr"
                then label, wikilink = gloss_lookup(gloss2, label, wikilink)
            end
    end
    local gloss_node
    if glossing_type == "no abbr"
        then gloss_node = mw.html.create("span")
    else gloss_node = mw.html.create("abbr") end
    gloss_node:addClass(conf.class.GlossAbbr)
    if label or wikilink then
        if not mw.ustring.match(gloss, "%l") -- excluding glosses that contain lower-case characters
            and not mw.ustring.match(gloss,conf.GlossSmallCapsExclude) -- and also excluding A, O etc. from rendering in small caps
            then gloss_node:attr("style", conf.style.GlossAbbr)
        end
        local abbr_label
        if label then abbr_label = label
            else abbr_label = wikilink end
        gloss_node:attr("title", abbr_label)
    	if data.abbreviations[gloss2] then
    		if data.abbreviations[gloss2].ambiguous then
    			gloss_node:addClass(conf.class.GlossAbbrAmb)
                    msg:add("ambiguous gloss")
                end
        end
        if glossing_type == "wikilink" and wikilink
        	then gloss_node:wikitext("[[", wikilink, "|" , gloss, "]]")
        	else gloss_node:wikitext(gloss) end
        if displaying_messages then -- logging gloss lookups:
        	local message = ""
        	if label then
        		message = "assuming " .. gloss2 .. " means \"" .. abbr_label .. "\";" end
    		if glossing_type == "wikilink" and wikilink then
    			message = message .. " linking to [[" .. wikilink .. "]];"
    		end
    		msg:add("gloss_message", message, gloss)
        end
    elseif glossing_type == "no abbr"
        then gloss_node
                :attr("style", conf.style.GlossAbbr)
                :wikitext(gloss)
    else
    	if displaying_messages then
    		msg:add("warning", "Gloss abbreviation " .. highlight(gloss2) .. "  not recognised" .. help_link("gloss abbr"))
        end
        msg:add("non-repeating error", "Unknown glossing abbreviation(s)" .. help_link("gloss abbr"))
        gloss_node
            :addClass(conf.class.GlossAbbrError)
            :addClass("error")
            :css("font-size", "100%")
            :attr("title", gloss2 .. ": glossing abbreviation not found")
            :attr("style", conf.style.ErrorMessage)
            :wikitext(gloss)
    end
    return tostring(gloss_node)
end

---------------------
-- find_gloss() parses a word into morphemes, and it calls format_gloss()
-- for anything that looks like a glossing abbreviation.
---------------------
local function find_gloss(word)
    local function scan_gloss(boundary, gloss_abbr) -- checks a morpheme if it is a gloss abbreviation
        if (mw.ustring.match(gloss_abbr, conf.GlossAbbrPattern)
            or conf.LowerCaseGlosses[gloss_abbr])
            and not (conf.GlossExcludeTable[gloss_abbr]
                or mw.ustring.match(gloss_abbr, conf.GlossExcludePattern))
            then gloss_abbr = format_gloss(gloss_abbr)
        end
        return boundary .. gloss_abbr
    end
    local word = mw.text.decode(word, true)
    if word == "I" -- for the case of the English word "I", the 1SG pronoun
        then return word end
    local pattern = "([" .. conf.GlossAbbrBoundary .. "]?)([^" .. conf.GlossAbbrBoundary .. "]+)"
    word = mw.ustring.gsub(word, pattern, scan_gloss) -- splits into morphemes
    return word
end

---------------------
-- The main purpose of the bletcherous parse() is to split a line into words and and then for each eligible word
-- to call find_gloss(). The parser outputs the individual words (with any gloss abbreviation formatting applied).
-- The simple job of splitting at whitespaces has been made complicated by a) the fact that the input can contain
-- whitespaces inside the various html elements that are the result of the application of various formatting templates;
-- and b) the need to be able to recognise the output of the template that formats custom gloss abbreviations
-- (and hence skip passing it on to find_gloss). See talk for a suggestion about its future.
---------------------
local function parse(cline, i, tags_found,ifglossing)

    local function issue_error(message, culprit)
    	UserMessages:add("error",  message .. ": ''" .. mw.ustring.sub(cline.whole, 1, i-1) .. "'''" .. culprit  .. "'''''")
    end
    if i > cline.length then return i end --this will only be triggered if the current line has less words than line 1
    local next_step, j, _, chunk
    local probe = mw.ustring.sub(cline.whole,i,i)
    if mw.ustring.match(probe,"[" .. conf.WordSeparator .. "]") and tags_found == 0
        then next_step =  i-1
    elseif probe == "[" then --Wikilink?
        if mw.ustring.sub(cline.whole,i+1,i+1) == "[" then
        	_,j,chunk = mw.ustring.find(cline.whole,"(%[%[.-%]%])", i)
        else chunk = "["; j = i end --not a wikilink then
        buffer = buffer .. chunk
        next_step =  parse(cline, j+1,tags_found,ifglossing)
    elseif probe == "{"  and tags_found == 0 then --curly brackets enclose a sequence of words to be treated as a single unit
    	_,j,chunk = mw.ustring.find(cline.whole,"(.-)(})", i+1)
    	if not chunk then
    		issue_error("Unclosed curly bracket", "{")
    		chunk = highlight("{"); j = i
        elseif ifglossing==true then
            chunk = find_gloss(chunk)
		else
			if cline.tone_sup then chunk = tone_sup(chunk) end
		end
    	buffer = buffer .. chunk
        next_step =  parse(cline, j+1,tags_found,ifglossing)
    elseif probe == "<" then -- We've encountered an HTML tag. What do we do now?
        local _,j,chunk = mw.ustring.find(cline.whole,"(<.->)",i)
        if not chunk then
        	issue_error("Unclosed angle bracket", "<")
        	chunk = highlight("<"); j = i
        elseif mw.ustring.sub(cline.whole,i,i+1) == "</" then -- It's a CLOSING tag
            if cline.glossing
                and ifglossing==false
                and mw.ustring.match(chunk,"</abbr>")
                then ifglossing=true end
            tags_found = tags_found - 1
        elseif not mw.ustring.match(chunk, "/>$") -- It's an OPENING tag, unless it opens a self-closing element (in which case the element is ignored)
            then if ifglossing == true -- the following checks for the output of {{ggl}}: 
                	and mw.ustring.find(chunk, conf.class.GlossAbbr, 1, true) -- it's important that the "find" function uses literal strings and not patterns
                		then ifglossing = false end
            tags_found = tags_found + 1
        end
        buffer = buffer .. chunk
        next_step = parse(cline, j+1,tags_found,ifglossing)
    else -- No HTML tags, so we only need to find where the word ends
        local _,k,chunk = mw.ustring.find(cline.whole,"(..-)([ <[])",i)
        if k then --ordinary text
            if ifglossing==true then
                buffer = buffer .. find_gloss(chunk)
			else
				if cline.tone_sup then chunk = tone_sup(chunk) end
				buffer = buffer .. chunk
            end
            next_step = parse(cline, k, tags_found, ifglossing)
        else -- reached end of string        
            if ifglossing == true then
                chunk = find_gloss(mw.ustring.sub(cline.whole,i))
            else
            	chunk = mw.ustring.sub(cline.whole,i)
            	if cline.tone_sup then chunk = tone_sup(chunk) end
            end
            buffer = buffer .. chunk
            next_step = cline.length
        end
    end
    return next_step
end
--------------------
-- The following function is called by Template:gcl and is used for formatting an individual glossing abbreviation
--------------------
function p.gcl(frame)
    local args = getArgs(frame,{
        trim = true,
        removeBlanks = false,
        parentOnly = true,
        wrappers = {'Template:Gcl'},
    })
    msg = UserMessages
    set_global_glossing_settings{style = args.style, underline = args.underline, small_caps = args['small-caps']}
    if not args.glossing then
        glossing_type = conf.GlossingType -- a global variable
    else glossing_type = set_glossing_type(args.glossing)
    end
    local gloss, label, wikilink = args[1], args[2], args[3]
    if not gloss then UserMessages:add("error", "No gloss supplied")
    	return UserMessages:print() end
    if wikilink and not args.glossing then -- if a wikilink is supplied and glossing isn't set to 'label'...
        glossing_type = 'wikilink' end --     .. then the wikilink will be formatted as such
    if label == "" then label = nil end
    if wikilink == "" then wikilink = nil end
    local result = format_gloss(gloss, label, wikilink)
    return result
end

--------------------
-- The following is the function called by Template:Interlinear.
-- It processes the template arguments, then calls parse() to split the input lines into words
-- and it then builds the output html.
--------------------
function p.interlinearise(frame)
---------------------
-- Prepare arguments
---------------------
    local if_auto_translit = false
    local args = getArgs(frame, { -- configuration for Module:Arguments
        trim = true,
        removeBlanks = false,
        parentFirst = true,
        wrappers = {'Template:Interlinear', 'Template:Fs interlinear'},
    })
    local template_name = frame:getParent():getTitle()
    if template_name == 'Template:Fs interlinear' then
        args.italics1 = args.italics1 or "no"
        args.italics2 = args.italics2 or "yes"
        args.glossing3 = args.glossing3 or "yes"
        if args.transl and not args.transl2 then args.transl2 = args.transl end
        if_auto_translit = true
        
    end
    local revid = frame:preprocess( "{{REVISIONID}}" )
    if  revid == "" then
    	if not args['display-messages'] or yesno(args['display-messages']) then
        displaying_messages = true end-- messages will be displayed only in preview mode
    end 
    msg = UserMessages
    local line = {}
    
    local function set_italics(n)
        line[n].attr.style = line[n].attr.style .. "font-style: italic;"
    	line[n].tone_sup = true -- single digits are assumed to be tone markers and will hence be superscripted
    	if args['tone-superscripting'] and not yesno(args['tone-superscripting'])
            then line[n].tone_sup = false end
    end
    
    if args.glossing then -- the glossing= parameter sets the default glossing type
    	local _gl = set_glossing_type(args.glossing)
    	if _gl then conf.GlossingType = _gl end
    end
    local _ablist = args.abbreviations or args.ablist
    if _ablist and _ablist ~= ""
        then set_custom_glosses(_ablist) end
        
    local _spacing = tonumber(args.spacing)
    if _spacing and _spacing <= 20
        then conf.style.WordDiv = conf.style.WordDiv .. 'margin-right: ' .. _spacing .. 'em;'
    else conf.style.WordDiv = conf.style.WordDiv .. conf.style.WordMargin
    end
    
    local offset, last_line = 0, 0
    for j,v in ipairs(args) do -- iterates over the unnamed parameters from the template
        last_line = last_line +1
        if is_empty(v)
            then offset = offset + 1
        else
        local i = j - offset
        line[i] = {}
        v = normalise(v)
        
        -- the following is part of a trial implementation of automatic transliteration:
        if if_auto_translit and v == "auto" and i > 1 then
            local source_line = line[i-1]
            local src_lang = source_line.lang
            if not src_lang then src_lang = args.lang end 
            if src_lang then
                   v = transliterate(source_line.whole, src_lang)
            else v = ""; msg:add("error", "No language specified for automatic transliteration")
            end
        end  -- end of trial block
        
        line[i].whole = v
        line[i].length = mw.ustring.len(v)
        
        local _c = args["c" .. i]
        if _c and _c ~= "" then
            line.hasComments = true
            line[i].c = _c
        end
        
        ---prepare style arguments----
        line[i].class = ""
        local _style = args["style" .. i]
        if not _style then _style = ""
        else _style = tidyCss(_style) end
        --line[i].attr holds the attributes for the <p> elements that enclose the words in line i
        line[i].attr = {style = conf.style.WordP .. _style}
                        
        local _lang = args["lang" .. i]
        if _lang and #_lang > 1 then
            line[i].lang = _lang
        else _lang = args.lang
            if _lang and #_lang > 1 and i == 1 then -- if a lang= parameter is supplied, it's assumed to apply to line 1
                line[i].lang = _lang
            end
        end
        line[i].attr.lang = line[i].lang
        
        if yesno(args["italics" .. i]) then
            set_italics(i)
        end
        
        local _transl = args["transl" .. i]
        if _transl and #_transl > 1 then
            _transl = mw.ustring.lower(_transl)
            local _lookup = lang_data.translit_title_table[_transl]
            if _lookup then
                if _lang and  _lookup[_lang] then
                    _transl = _lookup[_lang]
                else _transl = _lookup.default
                end
                if _transl then
                    line[i].attr.title = _transl
                end
            else  msg:add("error", "Transliteration scheme '" .. _transl .. "' not recognised")
            end
        end
        
        local _glossing = args["glossing" .. i]
        if _glossing then
            line[i].glossing = set_glossing_type(_glossing)
            line.HasCustomGlossing = true
        end
        
        local _ipa = args['ipa' .. i]
        if yesno(_ipa) then
            line[i].class = "IPA"
        end
        
        local _class = args['class' .. i]
        if _class then
            line[i].class = line[i].class .. " " .. _class
        end
        
        if line[i].class == ""
            then line[i].class = nil end
        end -- ends the first if-statement in the loop
    end -- ends the FOR cycle

    local line_count = #line
    if line_count == 0 then
        msg:add("error", template_name .. ": no lines supplied.")
        return msg:print_errors()
    end
    
    if line_count > 1 then
    	local _italics = args.italics
    	local n = tonumber(_italics)
    	if n and n > 0 then
    		set_italics(n)
		 elseif yesno(_italics) ~= false 
		 and yesno(args["italics" .. 1]) ~= false then
		 	set_italics(1) -- by default, the first line will get italicised
    	end
        -- the last unnamed parameter is assumed to be the free translation:
        free_translation = args[last_line]
        if not is_empty(free_translation) then
        	line [line_count] = nil   end  --... and is thus excluded from interlinearising
    end

-- If glossing isn't specified for any line, then it's chosen by default to occur
-- in the second line, unless only a single line has been supplied, in which case
-- the assumption is that it is the one containing grammatical glosses
	if yesno(args.glossing) == false then
		line.HasCustomGlossing = true end
	if not line.HasCustomGlossing then
		if line_count == 1 then
	    	line[1].glossing = conf.GlossingType
		elseif line[2] then
    		line[2].glossing = conf.GlossingType
    	end
    end
    set_global_glossing_settings{style = args['glossing-style'], underline = args.underline, small_caps = args['small-caps']}

---------------------
-- Segment lines into words
---------------------
    for i,v in ipairs(line) do
    	local ifglossing = false
        if line[i].glossing then
        	ifglossing = true -- if true the parser will attempt to format gloss abbreviations in the current line
    		glossing_type = line[i].glossing -- neccessarily a global variable
    	end
        local wc, n = 1, 1
        line[i].words = {}
        while n <= line[i].length do
            buffer = ""
            n = parse(line[i], n, 0, ifglossing)+2
            line[i].words[wc] = buffer
            wc = wc + 1
        end
    end

    ----Check for mismatches in number of words across lines----
    local number_of_words, mismatch_found = 0, false
    for i,v in ipairs(line) do -- find the maximum number of words in any line
        local wc = #line[i].words
        if wc ~= number_of_words then
            if i ~= 1 and wc ~= 0 then
                mismatch_found = true
            end
            if wc > number_of_words then
                number_of_words = wc
            end
        end
    end
    ----Deal with mismatches---
    if mismatch_found then
        local error_text = "Mismatch in the number of words between lines: "
        for i,v in ipairs(line) do
            local wc = #line[i].words
            error_text = error_text .. wc .. " word(s) in line " .. i .. ", "
            if wc ~= number_of_words then
                for current_word = wc+1, number_of_words do
                    line[i].words[current_word] = "&nbsp;"
                end
            end
        end
        if string.sub(error_text, -2) == ", "
            then error_text = string.sub(error_text, 1, #error_text - 2) .. " "
        end
        error_text = error_text .. help_link("mismatch")
        UserMessages:add("error", error_text)
    end

---------------------
-- Build the HTML
---------------------
    ---- If just a single line was supplied, format it as inline text
    if line_count == 1 then
        local span = mw.html.create('span')
        span:attr(line[1].attr)
        for wi = 1, number_of_words do
            local space
            if wi < number_of_words then space = " " else space = "" end
            span:wikitext(line[1].words[wi] .. space)
        end
        return tostring(span)
    end
    
    ---- More than one line supplied, so we'll produce interlinear display
    local div = mw.html.create("div")
    div:addClass(conf.class.Interlinear)
    if args.box and args.box ~= "" then
    	div:css("background-color", "#f8f9fa")
    		:css("border", "1px solid #eaecf0")
			:css("padding", "1em") end
    if args.top and args.top ~= "" then --lines to display above the interlinear block
    	div:tag("div")
    		:wikitext(args.top)
    end
    
    -- Producing the interlinear block
    for wi = 1, number_of_words do
        local div2 = div:tag("div")
                    :attr("style", conf.style.WordDiv)
        for i,_ in ipairs (line) do
            if line[i].whole ~= "" then -- skipping empty lines
                local p = div2:tag("p")
                p:attr(line[i].attr)
                if line[i].class then
                    p:addClass(line[i].class)
                end
                local _text = line[i].words[wi]
                if _text == "" or _text == " " 
                	then _text = "&nbsp;" end -- <p> elements without content mess up the interlinear display
                p:wikitext(_text)
            end
        end
    end
    
    --- If any "comments" have been specified, add them at the end of each line
    if line.hasComments then
        local divc = div:tag("div")
                    :attr("style", conf.style.WordDiv)
        for i,_ in ipairs (line) do
            local p = divc:tag("p")
            p:attr("style", conf.style.WordP)
            if line[i].c then
                p:wikitext(line[i].c)
            else p:wikitext("&nbsp;")
            end
        end
    end
    
    --Add hidden lines containing the content of each line of interlinear text: this is for accessibility
    for i,v in ipairs(line) do
        local hidden_line = div:tag("p")
        hidden_line:attr("style", conf.style.HiddenText)
                    :wikitext(v.whole)
    end
    
    -- Format the free translation
    local ft_line = div:tag("p")
    if free_translation and free_translation ~= "" then
    	ft_line:attr("style", "clear: left;")
        ft_line:wikitext(free_translation)
    end
    if args.bottom and args.bottom ~= ""
        then local bottom = div:tag('p')
        bottom:css('margin-top', '0')
        bottom:wikitext(args.bottom)
    end
    ft_line:node(msg:print_errors()) -- for error messages
    
    local end_div = div:tag("div")
        end_div:attr("style", conf.style.EndDiv)
    div:newline()
    
    -- For stuff to be displayed in the left margin, like example numbering
    local before, number, indent = "", nil, nil
    if args.number and args.number ~= "" 
    	then number = args.number end
    if args.indent and args.indent ~=""
    	then indent = args.indent end
    if indent or number then
    	if not indent then indent = "3" end --default value
    	div:css("margin-left", indent .. 'em')
    	if number then 
    		before = '<div style="position:absolute; left:1em;">' .. args.number .. '</div>'
    	end
	end
    return  before .. tostring(div) .. msg:print_warnings()
end

return p