Module:Clade

From Summertime Saga Wiki
Revision as of 18:00, 14 June 2019 by Casiope (talk | contribs)
Jump to: navigation, search

This module is designed to be used with the template {{clade}} to draw phylogenetic trees or cladograms. The new template‐module combination extends the feature available with {{clade}} while replicating the behavior of the older templates written with the template language.

Additionally, the module has the code for a prototype template, which converts Newick strings into clade structure. This is not to be used in Wikipedia articles, but is a utility to help construct cladograms for inclusion in articles.

The diagram below gives an overview of the features. See the template documentation for a more detailed description of how to use the templates.

Usage

{{#invoke:Clade|main|style={{{style}}}}}


--[[NOTE: this module contains functions for generating the table structure of the clade tree: 

The main function is called by the template using the {{invoke}} instruction; the three main functions are:
        p.main(frame) - opens and closes table, loops through the children of node, main is invoked once and controls the rest, calling ...
        p.addTaxon(childNumber, nodeLeaf) - the nuts and bolts; code dealing with each child node
        p.addLabel(childNumber) - adds the label text
        
        now uses templatestyles
]]

local p = {}



--[[============================== main function  ===========================
-- main function, which will generate the table structure of the tree

Test version:
Usage: {{#invoke:Module:Sandbox/Jts1882/CladeN|main|style={{{STYLE|}}} }}
Template:CladeN

Release version:
Usage: {{#invoke:Clade|main|style={{{STYLE|}}} }}
Template:Clade
]]

function p.main(frame)

	local cladeString = ""	
	local maxChildren = 20 -- currently 17 in the clade/cladex templates
	local childNumber = 0
    lastNode = 0 -- make this global for now (declaring here prevents lua error for nil value in template)
	--local nodeCount = 0 -- total leafs plus new clade branches
	--local leafCount = 0 -- just the terminal leaves
	--local cladeCount = 0 -- new clade calls (each with a table)
	local childCount = 0 -- number of leaves in the clade (can use to set bottom of bracket in addTaxon()
	local totalCount = 0
	
    infoOutput = p.getCladeTreeInfo() -- get info about clade structure, e.g. lastNode (last |N= child number)
    
    --[[ add the templatestyles tag  
          add to every clade table >> 137 times in Passeriformes test page
           cladeCount==1 >>  43 insertions
           nodeCount==2 >>  5 insertions (once for the style css, one link in each cladegram)
                            2 in Neosuchia (one for style css, one link for second cladogram)
                            this is because it counts "dummy" clades and outer clades with one leaf
           cladeCount==1 AND nodeCount==2 >>  1 insertions 
              but none in Neosuchia
    --]]
    -- worked for wrong reasons if nodeCount==2  then 
    if cladeCount==1  then
    	cladeString = cladeString .. p.templateStyle( frame, src ) .. '\n'
    end	
    
    
	local tableStyle = frame.args.style or ""
	--if tableStyle == '{{{style}}}' then tableStyle = "" end -- no longer needed as pipe added to template to suppress passing of {{{style}} when no value
	
	if tableStyle ~= "" then
		tableStyle = ' style="' .. tableStyle .. '"' -- include style= in string to suppress empty style elements
	end
    
    reverseClade =frame.args.reverse or false -- a global
    --ENFORCE GLOBAL FOR DEVELOPMENT
    --reverseClade = true

	local captionName =mw.getCurrentFrame():getParent().args['caption'] or ""
	local captionStyle = mw.getCurrentFrame():getParent().args['captionstyle'] or ""

    -- add an element to mimick nowiki WORKS BUT DISABLE FOR DEMO PURPOSES
    --cladeString = '<p class="mw-empty-elt"></p>\n'
    
	-- open table	
	-- (border-collapse causes problems (see talk) -- cladeString = cladeString .. '{| style="border-collapse:collapse;border-spacing:0;margin:0;' .. tableStyle .. '"'
    -- (before CSS styling) -- cladeString = cladeString .. '{| style="border-spacing:0;margin:0;' .. tableStyle .. '"'
    cladeString = cladeString .. '{|class="clade"' .. tableStyle

    -- add caption
	if captionName ~= "" then
		cladeString = cladeString .. '\n|+ style="' .. captionStyle .. '"|' .. captionName
	end
	
	-- global nodeParameters (unnumber, i.e. color, thickness, state) apply to whole node bracket, 
	--    but can be overrriden by branchParameters (numbered, e.g. color2, thickness2, state2)
	nodeColor = mw.getCurrentFrame():getParent().args['color'] or ""
	nodeThickness = tonumber(mw.getCurrentFrame():getParent().args['thickness']) or 1
	nodeState = mw.getCurrentFrame():getParent().args['state'] or "solid"
	
	local moreNeeded = true
	childNumber = 0
	--lastNode = 0

	--[[get child elements (add more rows for each child of node; each child is two rows)
	    the function addTaxon is called to add the rows for each child element;
	    each child add two rows: the first cell of each row contains the label or sublabel (below the line label), respectively;
	    the second cell spans both rows and contains the leaf name or a new clade structure
	    a third cell on the top row is sometimes added to contain a group  to the right
	]]
	
	-- main loop
	while 	childNumber < lastNode do -- use the last number determined in the preprocessing

		childNumber = childNumber + 1 -- so we start with 1
		local nodeLeaf = mw.getCurrentFrame():getParent().args[tostring(childNumber)] or ""  -- get data from |N=
		local nodeLabel = mw.getCurrentFrame():getParent().args['label'..tostring(childNumber)] or ""  -- get data from |labelN=
		
		
		local newickString = mw.getCurrentFrame():getParent().args['newick'..tostring(childNumber)] or ""  -- get data from |labelN=
		if newickString ~= "" then -- if using a newick string instead of a clade structure
			if nodeLabel == "" then -- use labelN by default, otherwise use root name from Newick string
				nodeLabel = p.getNewickOuterterm(newickString) -- need to use terminal part of newick string for label
			end
			cladeString = cladeString .. '\n' .. p.addTaxon(childNumber, p.newick(0, newickString), nodeLabel, lastNode)
			--lastNode=lastNode+1 -- there is a counting problem with the newickstring
		elseif nodeLeaf ~= "" then -- if the node contains a leaf name or clade structue
			--if reverseClade2 then
			--	cladeString = cladeString .. '\n' .. p.addTaxonReverse(childNumber, nodeLeaf, nodeLabel, lastNode)
		    --else
				cladeString = cladeString .. '\n' .. p.addTaxon(childNumber, nodeLeaf, nodeLabel, lastNode)
			--end
		end
	end

	local footerText  = mw.getCurrentFrame():getParent().args['footer'] or ""
	local footerStyle = mw.getCurrentFrame():getParent().args['footerstyle'] or ""

	if footerText ~= "" then
	   cladeString = cladeString ..  '\n|-style="' .. footerStyle .. '"\n|colspan="2"|<p>' .. footerText .. '</p>||'
	   -- note the footer causes a problem with tr:last-child so need either
	   -- (1) use <tfoot> but it is not allowed or incompatable
	   --           cladeString = cladeString ..  '<tfoot><tr style="' .. footerStyle .. '"><td colspan="2"><p>' .. footerText .. '</p></td></tr></tfoot>'
	   -- (2) always add footer and use nth:last-child(2) but is this backwards compatible
	   -- (3) if footer= set the style inline for the last sublabel row (more a temp fix)
	   -- (4) set class for first and last element (DONE. Also works well with reverse class)
	end

	-- close table (wikitext to close table)
	cladeString = cladeString ..  '\n|}'
	
	return cladeString
	--return '<div style="width:auto;">\n' .. cladeString .. '</div>'
end

--[[ function to add child elements
     adds wikitext for two rows of the table for each child node, 
     	the first cell in each is used for the label and sublabel; the bottom border forms the horizonal branch of the bracket
     	the second cell is used for the leafname or a transcluded clade structure and spans both rows
     note that the first and last child nodes need to be handled differently from the middle elements
	     the middle elements (|2, |3 ...) use a left border to create the vertical line of the bracket
	     the first child element doesn't use a left border for the first cell in the top row (as it is above the bracket)
	     the last child doesn't use a left border for the first cell in the second row (as it is below the bracket)
]]
function p.addTaxon(childNumber, nodeLeaf, nodeLabel, lastNode)

	-- get border formating parameters for branch (default to global nodeParameters)
	--    - the branch parameters have a number, e.g. |colorN, |thicknessN, |stateN
	--    - the node parameters have no number, e.g. |color, |thickness, |state
	local branchThickness = tonumber(mw.getCurrentFrame():getParent().args['thickness'..tostring(childNumber)]) or nodeThickness
	local branchColor = mw.getCurrentFrame():getParent().args['color'..tostring(childNumber)] or nodeColor
	local branchStyle = mw.getCurrentFrame():getParent().args['style'..tostring(childNumber)] or ""
	local branchState = mw.getCurrentFrame():getParent().args['state'..tostring(childNumber)] or nodeState -- "solid" 
	if branchState == 'double' then if branchThickness < 2 then branchThickness = 3 end end  -- need thick line for double

    -- the left border takes node parameters, the bottom border takes branch parameters
    -- this has coding on the colours for green on black
   local bottomBorder =  tostring(branchThickness) ..'px ' .. branchState  .. (branchColor~="" and ' ' .. branchColor or '')
   local leftBorder   =  tostring(nodeThickness)   ..'px ' .. nodeState  .. (nodeColor~="" and ' ' .. nodeColor or '')
	
	--The default border styles are in the CSS (styles.css)
	--    the inline styling is applied when thickness, color or state are change
	
	local useInlineStyle = false
	-- use inline styling non-default color, line thickness or state have been set
	if branchColor ~= "" or branchThickness ~=  1 or 	branchState ~= "solid"  then
		useInlineStyle = true
	end
	
	
	-- variables for right hand bar or bracket
	--local barColor  = "" 
	local barRight  = mw.getCurrentFrame():getParent().args['bar'..tostring(childNumber)] or "0"
	local barBottom = mw.getCurrentFrame():getParent().args['barend'..tostring(childNumber)] or "0"
	local barTop    = mw.getCurrentFrame():getParent().args['barbegin'..tostring(childNumber)] or "0"
	local barLabel  = mw.getCurrentFrame():getParent().args['barlabel'..tostring(childNumber)] or ""
	local groupLabel  = mw.getCurrentFrame():getParent().args['grouplabel'..tostring(childNumber)] or ""
	local groupLabelStyle  = mw.getCurrentFrame():getParent().args['labelstyle'..tostring(childNumber)] or ""

	--replace colours with format string; need right bar for all three options
	if barRight  ~= "0" then barRight  = "2px solid " .. barRight  end 
	if barTop    ~= "0" then barRight  = "2px solid " .. barTop    end
	if barBottom ~= "0" then barRight  = "2px solid " .. barBottom end 
	if barTop    ~= "0" then barTop    = "2px solid " .. barTop    end
	if barBottom ~= "0" then barBottom = "2px solid " .. barBottom end 
	
	
	-- now construct wikitext 
	local cladeString = ''
	local styleString = ''
    local borderStyle = '' -- will be used if border color, thickness or state is to be changed
    local classString = ''
    local reverseClass = ''
    
    -- class to add if using reverse (rtl) cladogram; 
    if reverseClade then reverseClass = ' reverse' end 
    
    -- (1) wikitext for new row
    --cladeString = cladeString .. '\n|-'

	-- (2) now add cell with label
    
	if useInlineStyle then
		if childNumber == 1 then
	        borderStyle = 'border-left:none;border-right:none;border-bottom:' .. bottomBorder .. ';' 
	        --borderStyle = 'border-bottom:' .. bottomBorder .. ';' 
	 	else -- for 2-17
	 		if reverseClade then
	    		borderStyle  = 'border-left:none;border-right:' .. leftBorder .. ';border-bottom:' .. bottomBorder .. ';' 
	 	    else
	    		borderStyle  = 'border-left:' .. leftBorder .. ';border-bottom:' .. bottomBorder .. ';' 
	    	end
		end
	end

   	if useInlineStyle or (branchStyle ~= '') then
   		styleString = 'style="' .. borderStyle .. branchStyle .. '"'
    end	

	if childNumber == 1 then
		classString= 'class="clade-label first" '    -- add class first for top row
    else
    	classString = 'class="clade-label' .. reverseClass .. '" ' -- add "reverse" class if ltr cladogram
    end

    --  wikitext for cell with label
    local labelCellString = '\n|' .. classString .. styleString  .. '|' .. p.addLabel(childNumber,nodeLabel) -- p.addLabel(nodeLabel)
    
    --cladeString = cladeString .. labelCellString

	---------------------------------------------------------------------------------
	-- (3) add cell with leaf (which may be a table with transluded clade content)
    
    if barRight  ~= "0"  then 
    	if reverseClade then -- we want the bar on the left
    		styleString = ' style="border-left:' .. barRight .. ';border-bottom:' .. barBottom .. ';border-top:' .. barTop .. ';' .. branchStyle .. '"'
    	else
    		styleString = ' style="border-right:' .. barRight .. ';border-bottom:' .. barBottom .. ';border-top:' .. barTop .. ';' .. branchStyle .. '"'
    	end
    else
    	if (branchStyle ~= '') then
    		styleString = ' style="' .. branchStyle .. '"'
        else
        	styleString = '' -- use defaults in styles.css
        end
    end

    -- wikitext for leaf cell (note: nodeLeaf needs to be on newline if new wikitable)

    classString = 'class="clade-leaf' .. reverseClass .. '"'

    local leafCellString = '\n|rowspan=2 ' .. classString  .. styleString .. ' |\n' .. nodeLeaf 
   
    --cladeString = cladeString .. leafCellString

    
    -------------------------------------------
    -- (4) stuff for right-hand bracket labels

  	classString='class="clade-bar' .. reverseClass .. '"'
    	
    local barLabelCellString = ''
	if barRight  ~= "0"  and barLabel ~= "" then 
	   barLabelCellString = '\n|rowspan=2 ' .. classString .. ' |' .. barLabel
	else -- uncomment following line to see the cell structure
	   --barLabelCellString = '\n|rowspan=2 ' .. classString .. ' |' .. 'BL'
	end 
	
	if groupLabel ~= "" then 		
		barLabelCellString = barLabelCellString .. '\n|rowspan=2 ' .. classString .. ' style="'.. groupLabelStyle .. '" |' .. groupLabel 
	else -- uncomment following line to see the cell structure
	    --barLabelCellString = barLabelCellString .. '\n|rowspan=2 ' .. classString .. '" |' .. 'GL' 
   end 	

   --cladeString = cladeString .. barLabelCellString
    
	-------------------------------------------------------------------------------------
	-- (5) add second row (only one cell needed for sublabel because of rowspan=2); 
	--     note: earlier versions applied branch style to row rather than cell
	--           for consistency, it is applied to the sublabel cell as with the label cell
	
	--cladeString = cladeString .. '\n|-' 
	
	-----------------------------------
	-- (6) add cell containing sublabel
	
	local subLabel = mw.getCurrentFrame():getParent().args['sublabel'..tostring(childNumber)] or ""  -- request in addLabel
	
	-- FOR TESTING: use subLabel for annotating the clade structues to use structure information (DEBUGGIING ONLY)
	--if childNumber==lastNode then subLabel= infoOutput end
	-- END TESTING
	
	borderStyle = ''
	styleString = ''
	if useInlineStyle then
		if childNumber==lastNode then 		-- if childNumber==lastNode we don't want left border, otherwise we do
			borderStyle = 'border-right:none;border-left:none;'
	    else
	 		if reverseClade then
	    		borderStyle  = 'border-left:none;border-right:' .. leftBorder .. ';' 
	 	    else
	    		borderStyle  = 'border-right:none;border-left:' .. leftBorder .. ';'  
	    	end
	    end 
    end
    if borderStyle ~= '' or  branchStyle ~= '' then         
       styleString = ' style="' .. borderStyle .. branchStyle .. '"'
    end

    --local sublabel = p.addLabel(childNumber,subLabel)

    if childNumber == lastNode then 
    	classString = 'class="clade-slabel last" ' 
    else
        classString = 'class="clade-slabel' .. reverseClass .. '" '
    end
    local sublabelCellString = '\n|' .. classString .. styleString .. '|' ..  p.addLabel(childNumber,subLabel)
    
    --cladeString = cladeString .. sublabelCellString

    -- constuct child element wikitext
    if reverseClade then
	    cladeString = cladeString .. '\n|-'
	    cladeString = cladeString .. barLabelCellString
	    cladeString = cladeString .. leafCellString
	    cladeString = cladeString .. labelCellString
		cladeString = cladeString .. '\n|-'     
		cladeString = cladeString .. sublabelCellString
    else	
	    cladeString = cladeString .. '\n|-'
	    cladeString = cladeString .. labelCellString
	    cladeString = cladeString .. leafCellString
	    cladeString = cladeString .. barLabelCellString
		cladeString = cladeString .. '\n|-'     -- add second row (only one cell needed for sublabel because of rowspan=2);
		cladeString = cladeString .. sublabelCellString
    end
    
	return cladeString
end

---------------------------------------------------------------------------------------------
--[[ reverse version;
         currently duplicates the code changing order of elements (bars, leaf, label, sublabel)
         variables border-right
--]]
function p.addTaxonReverse(childNumber, nodeLeaf, nodeLabel, lastNode)

	-- (1) get formating parameters for branch (default to global nodeParameters)
	--    - the branch parameters have a number, e.g. |colorN, |thicknessN, |stateN
	--    - the node parameters have no number, e.g. |color, |thickness, |state
	local branchThickness = tonumber(mw.getCurrentFrame():getParent().args['thickness'..tostring(childNumber)]) or nodeThickness
	local branchColor = mw.getCurrentFrame():getParent().args['color'..tostring(childNumber)] or nodeColor
	local branchStyle = mw.getCurrentFrame():getParent().args['style'..tostring(childNumber)] or ""
	local branchState = mw.getCurrentFrame():getParent().args['state'..tostring(childNumber)] or nodeState -- "solid" 
	if branchState == 'double' then if branchThickness < 2 then branchThickness = 3 end end  -- need thick line for double

    -- the left border takes node parameters, the bottom border takes branch parameters
   local bottomBorder =  tostring(branchThickness) ..'px ' .. branchState  .. (branchColor~="" and ' ' .. branchColor or '')
   local leftBorder   =  tostring(nodeThickness)   ..'px ' .. nodeState  .. (nodeColor~="" and ' ' .. nodeColor or '')
	
	-- variables for right hand bar or bracket
	--local barColor  = "" 
	local barRight  = mw.getCurrentFrame():getParent().args['bar'..tostring(childNumber)] or "0"
	local barBottom = mw.getCurrentFrame():getParent().args['barend'..tostring(childNumber)] or "0"
	local barTop    = mw.getCurrentFrame():getParent().args['barbegin'..tostring(childNumber)] or "0"
	local barLabel  = mw.getCurrentFrame():getParent().args['barlabel'..tostring(childNumber)] or ""
	local groupLabel  = mw.getCurrentFrame():getParent().args['grouplabel'..tostring(childNumber)] or ""
	local groupLabelStyle  = mw.getCurrentFrame():getParent().args['labelstyle'..tostring(childNumber)] or ""

	--replace colours with format string; need right bar for all three options
	if barRight  ~= "0" then barRight  = "2px solid " .. barRight  end 
	if barTop    ~= "0" then barRight  = "2px solid " .. barTop    end
	if barBottom ~= "0" then barRight  = "2px solid " .. barBottom end 
	if barTop    ~= "0" then barTop    = "2px solid " .. barTop    end
	if barBottom ~= "0" then barBottom = "2px solid " .. barBottom end 
	
	-- now construct wikitext 
	local styleString = ''
	local cladeString = ''
   
    -- (1) wikitext for new row
    cladeString = cladeString .. '\n|-'

    -- (4) stuff for right-hand brackets (or left hand brackets in reverse version)

	-- note: it might be useful to add rowspan=2 to allow full range of positions
	if groupLabel ~= ""then 
		cladeString = cladeString .. '\n|rowspan=2 class="clade-bar" style="'.. groupLabelStyle .. '" |' .. groupLabel  
	end
    --[[ don't use bar label in the reverse version (probably best to get rid altogether)
	if barRight  ~= "0"  then 
    	cladeString = cladeString .. '&nbsp;&nbsp;'            -- add spaces between leaf text and bar
		if barLabel ~= "" then 
			cladeString = cladeString .. '\n|rowspan=2 style="vertical-align:middle;" |' .. barLabel 
		end
	end 
    --]]


	-- (3) add cell with leaf (which may be a table with transluded clade content)
    --cladeString = cladeString .. '|| rowspan=2 style="border: 0; padding: 0; border-right: ' .. barRight .. ';border-bottom: ' .. barBottom .. ';border-top: ' .. barTop .. ';' .. branchStyle .. ' " | '
	styleString = ''  
    if barRight  ~= "0"  then 
    	--(before CSS styling) -- styleString = 'style="border:0;padding:0;border-right:' .. barRight .. ';border-bottom:' .. barBottom .. ';border-top:' .. barTop .. ';' .. branchStyle .. '"'
    	styleString = ' style="border-left:' .. barRight .. ';border-bottom:' .. barBottom .. ';border-top:' .. barTop .. ';' .. branchStyle .. '"'
    else
    	--(before CSS styling) -- styleString = 'style="border:0;padding:0;' .. branchStyle .. '"'
    	if (branchStyle ~= '') then
    		styleString = ' style="' .. branchStyle .. '"'
        end
    end
    
    -- add wikitext for leaf cell (note: nodeLeaf needs to be on newline if new wikitable)
    cladeString = cladeString .. '\n|rowspan=2 class="clade-leafR"' .. styleString .. '|\n' .. nodeLeaf 
    --cladeString = cladeString .. '\n' .. nodeLeaf  -- needs to be on newline if new wikitable

	-- (2) now add cell with label
	styleString = ''
	if childNumber == 1 then
		-- the width gives minimum spacing when all labels are empty (was 1.5em)
		--(before CSS styling) -- styleString = ' style="width:1em;border:0;padding:0 0.2em;border-bottom:' .. bottomBorder .. ';vertical-align:bottom;text-align:center;' .. branchStyle .. '"'
        styleString = 'style="border-bottom:' .. bottomBorder .. ';' .. branchStyle .. '"'
 	else -- for 2-17
    	--(before CSS styling) -- styleString = ' style="border:0;padding:0 0.2em;border-left:' .. leftBorder .. ';border-bottom:' .. bottomBorder .. ';vertical-align:bottom;text-align:center;' .. branchStyle .. '"'
    	styleString = 'style="border-right:' .. leftBorder .. ';border-bottom:' .. bottomBorder .. ';' .. branchStyle .. '"'
	end
    --  add wikitext for cell with label
    cladeString = cladeString .. '\n|class="clade-label" ' .. styleString  .. '|' .. p.addLabel(childNumber,nodeLabel) -- p.addLabel(nodeLabel)

    
 	

	-- (5) add second row (only one cell needed for sublabel because of rowspan=2) 
	
    local branchStyleString = 'style="' .. branchStyle .. '"  ' 
    if branchStyle == '' then branchStyleString = ''  end -- avoid empty style elements
	cladeString = cladeString .. '\n|-' .. branchStyleString
		
    local subLabel = mw.getCurrentFrame():getParent().args['sublabel'..tostring(childNumber)] or ""  -- request in addLabel
	
	-- FOR TESTING: use subLabel for annotating the clade structues to use structure information (DEBUGGIING ONLY)
	--subLabel= '(N=' .. infoOutput .. ')'
	-- END TESTING
	
	-- (6) add cell containing sublabel
	if childNumber~=lastNode then -- if childNumber==lastNode we don't want left border, otherwise we do
		styleString = ' style="border-right:' .. leftBorder ..';"'
    else
	    --cladeString = cladeString ..  '\n' .. '| style="border: 0; padding: 0; vertical-align: top;" | <br/> '
	    --(before CSS styling) -- styleString = 'style="border:0;vertical-align:top;text-align:center;"'
		styleString = ''
    end 
    
    local sublabel = p.addLabel(childNumber,subLabel)
    local classString = '' -- class only needed if there is a label to display
    if sublabel ~= '<br/>' then classString = 'class="clade-slabel" ' end
    
    cladeString = cladeString .. '\n|' .. classString .. styleString .. '|' ..  sublabel
    
	return cladeString
end


--[[ adds text for label or sublabel to a cell
]]
function p.addLabel(childNumber,nodeLabel)
	
	--local nodeLabel = mw.getCurrentFrame():getParent().args['label'..tostring(childNumber)] or ""

	--local firstChars = string.sub(nodeLabel, 1,2) -- get first two characters; will be {{ if no parameter (for Old method?)
	--if firstChars == "{{{" or nodeLabel == "" then
	if nodeLabel == "" then
		--return '<br/>' --'&nbsp;<br/>'  -- remove space to reduce post-expand include size (the width=1.5em handles spacing)
		--return '<br/>' -- must return something; this is critical for clade structure 
		return '&#8239;' -- &nbsp; &thinsp; &#8239;(thin nbsp)
	else
		-- spaces can cause  wrapping and can break tree structure, hence use span with nowrap class
		--return '<span class="nowrap">' .. nodeLabel .. '</span>'
		
		-- a better method for template expansion size is to replace spaces with nonbreaking spaces
		-- however, there is a problem if labels have a styling element (e.g. <span style= ..., <span title= ...)
		local stylingElementDetected = false
		if string.find(nodeLabel, "span ") ~= nil  then  stylingElementDetected = true end
		if string.find(nodeLabel, " style") ~= nil then stylingElementDetected = true end 
		
		if stylingElementDetected == true then 
			return '<span class="nowrap">' .. nodeLabel .. '</span>'
    	else	
    		local nowrapString = string.gsub(nodeLabel," ", "&nbsp;") -- replace spaces with non-breaking space
			                       -- could replace hyphen with non-breaking hyphen (&#8209;)
			return nowrapString
		end
	end
end



--[[=================== Newick string handling function =============================
]]
function p.getNewickOuterterm(newickString)
	return string.gsub(newickString, "%b()", "")   -- delete parenthetic term
end

function p.newick(count,newickString)
	
	local cladeString = ""
	count = count+1
	--start table
	--cladeString = cladeString .. '{| style="border-collapse:collapse;border-spacing:0;border:0;margin:0;'
	cladeString = cladeString .. '{| class="clade" '
	
	local j,k
	j,k = string.find(newickString, '%(.*%)')                 -- find location of outer parenthesised term
	local innerTerm = string.sub(newickString, j+1, k-1)      -- select content in parenthesis
	local outerTerm = string.gsub(newickString, "%b()", "")   -- delete parenthetic term
	if outerTerm == 'panthera' then outerTerm = "x" end     -- how is this set in local variable for inner nodes?
	
	outerTerm = tostring(count)
	
	-- need to remove commas in bracket terms before split, so temporarily replace commas between brackets
    local innerTerm2 =  string.gsub(innerTerm, "%b()",  function (n)
                                         	return string.gsub(n, ",%s*", "XXX")  -- also strip spaces after commas here
                                            end)
	--cladeString = cladeString .. '\n' .. p.addTaxon(1, innerTerm2, "")

    -- this needs a lastNode variable
	local s = strsplit(innerTerm2, ",")
	--oldLastNode=lastNode
	local lastNode=table.getn(s) -- number of child branches
	local i=1	
	while s[i] do	
		restoredString = string.gsub(s[i],"XXX", ",")   -- convert back to commas
		--restoredString = s[i]
		local outerTerm = string.gsub(restoredString, "%b()", "")
		if string.find(restoredString, '%(.*%)') then
			--cladeString = cladeString .. '\n' .. p.addTaxon(i, restoredString, "x")
			cladeString = cladeString .. '\n' .. p.addTaxon(i, p.newick(count,restoredString), outerTerm, lastNode)
			-- p.addTaxon(2, p.newick(count,newickString2), "root")
		else
			cladeString = cladeString .. '\n' .. p.addTaxon(i, restoredString, "", lastNode) --count)
		end
		i=i+1
	end
   -- lastNode=oldLastNode
    
	-- close table
	--cladeString = cladeString ..  '\n' .. '| style="border: 0; padding: 0; vertical-align: top;" | <br/> \n|}'
	--cladeString = cladeString ..  '\n| <br/> \n|}' -- is this legacy for extra sublabel?
	cladeString = cladeString ..  '\n|}'
	return cladeString
end
-- emulate a standard split string function
function strsplit(inputstr, sep) 
        if sep == nil then
                sep = "%s"
        end
        local t={} ; i=1
        for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
                t[i] = str
                i = i + 1
        end
        return t
end


-- =================== experimental Newick to clade parser function =============================

--[[Function of convert Newick strings to clade format

Usage: {{#invoke:Module:Sandbox/Jts1882/CladeN|newickConverter|newickstring={{{NEWICK_STRING}}} }}
]]
function p.newickConverter(frame)
	
	local newickString = frame.args['newickstring']
	
	
	if newickString == '{{{newickstring}}}' then return newickString  end

	-- show the Newick string
	local cladeString = ''
	local levelNumber = 1           --  for depth of iteration
	local childNumber = 1           --  number of sister elements on node  (always one for root)
	
	--  converted the newick string to the clade structure
	cladeString = cladeString .. '{{clade'
	cladeString = cladeString .. p.newickParseLevel(newickString, levelNumber, childNumber) 
	cladeString = cladeString .. '\r}}'  

	local resultString = ''
    local option = mw.getCurrentFrame():getParent().args['option'] or ''
    if option == 'tree' then
	 	--show the transcluded clade diagram
		resultString =   cladeString    	
    else
    	-- show the Newick string
		resultString = '<pre>'..newickString..'</pre>'	
	    -- show the converted clade structure
	    resultString = resultString .. '<pre>'.. cladeString ..'</pre>'	
    end
    --resultString = frame:expandTemplate{ title = 'clade',  frame:preprocess(cladeString) }

    return resultString
end

--[[ Parse one level of Newick string
     This function receives a Newick string, which has two components
      1. the right hand term is a clade label: |labelN=labelname
      2. the left hand term in parenthesis has common delimited child nodes, each of which can be
           i.  a taxon name which just needs:  |N=leafname 
           ii. a Newick string which needs further processing through reiteration
]]
function p.newickParseLevel(newickString,levelNumber,childNumber)

    
	local cladeString = ""
	local indent = p.getIndent(levelNumber) 
	--levelNumber=levelNumber+1
	
	local j=0
	local k=0
	j,k = string.find(newickString, '%(.*%)')                 -- find location of outer parenthesised term
	local innerTerm = string.sub(newickString, j+1, k-1)      -- select content in parenthesis
	local outerTerm = string.gsub(newickString, "%b()", "")   -- delete parenthetic term

	cladeString = cladeString .. indent .. '|label'..childNumber..'='  .. outerTerm
	cladeString = cladeString .. indent .. '|' .. childNumber..'='  .. '{{clade'

	levelNumber=levelNumber+1
	indent = p.getIndent(levelNumber)
	
		-- protect commas in inner parentheses from split; temporarily replace commas between parentheses
	    local innerTerm2 =  string.gsub(innerTerm, "%b()",  function (n)
	                                         	return string.gsub(n, ",%s*", "XXX")  -- also strip spaces after commas here
	                                            end)
	
		local s = strsplit(innerTerm2, ",")
		local i=1	
		while s[i] do	
			restoredString = string.gsub(s[i],"XXX", ",")   -- convert back to commas
	
			local outerTerm = string.gsub(restoredString, "%b()", "")
			if string.find(restoredString, '%(.*%)') then
				--cladeString = cladeString .. indent .. '|y' .. i .. '=' .. p.newickParseLevel(restoredString,levelNumber+1,i) 
				cladeString = cladeString  .. p.newickParseLevel(restoredString,levelNumber,i) 
			else
				cladeString = cladeString .. indent .. '|' .. i .. '=' .. restoredString --.. '(level=' .. levelNumber .. ')'
			end
			i=i+1
		end
--    end -- end splitting of strings

	cladeString = cladeString .. indent .. '}}'  
    return cladeString
end

function p.getIndent(levelNumber)
	local indent = "\r"
	local extraIndent = mw.getCurrentFrame():getParent().args['indent'] or 0
	
	while tonumber(extraIndent) > 0 do
	    indent = indent .. " " -- an extra indent to make aligining compound trees easier
	    extraIndent = extraIndent - 1
	end
	
	while levelNumber > 1 do
		indent = indent .. "   "
		levelNumber = levelNumber-1
	end
	return indent
end

function p.newickstuff(newickString)

	
end

------------------------------------------------------------------------------------------


function p.test2(target)
	local target ="User:Jts1882/sandbox/templates/Template:Passeroidea"
	local result = mw.getCurrentFrame():expandTemplate{ title = target, args = {['style'] = '' } }
	return result
end
-------------------------------------------------------------------------------------------

function p.toggle(frame)
	
	if 1==2 then return 'some text' end
	
	--local toggleSymbol = 'toggle all'
	local toggleSymbol = mw.getCurrentFrame():getParent().args['button'] or ""

	local toggleString = '<div class="'
                
    local i=0
    while 	i < 20 do  -- limit on number of toggle elements controlled by the trigger button
    	i = i + 1 -- so we start with 1
		local target = mw.getCurrentFrame():getParent().args['id'..tostring(i)] 
	    
	    -- add classes for the three elements of each target: expand symbol, collapse symbol and contents
	    if target ~= nil then
            toggleString = toggleString .. ' mw-customtoggle-myClade' .. target 
                ..             ' mw-customtoggle-collapseSymbol' .. target 
                ..             ' mw-customtoggle-expandSymbol' .. target 
        end
    end
  
 
 toggleString = toggleString  ..  '">' .. toggleSymbol .. '</div>'

  return toggleString
end
-----------------------------------------------------------------------------------------------
function p.hidden(frame)
    
    local id = mw.getCurrentFrame():getParent().args['id'] or ""
    local mode = mw.getCurrentFrame():getParent().args['mode'] or "right"
    local expandSymbol = mw.getCurrentFrame():getParent().args['expand-symbol'] or "&#8862;"
    local collapseSymbol = mw.getCurrentFrame():getParent().args['collapse-symbol'] or "&#8863;"
    local initialState = mw.getCurrentFrame():getParent().args['expanded']
    
    -- default is content collapsed
    local contentState = " mw-collapsed" -- class to collapse content at start
    local collapseSymbolState = " mw-collapsed"
    local expandSymbolState = ""
    if initialState then
       contentState = ""
       collapseSymbolState =  ""
       expandSymbolState = " mw-collapsed" 
    end
    	
	
    -- collapsible element containing the expand sympol and/or text
    local expandSymbolString = '<td style="padding:0 0 0.25em 0;">' 
                .. '<div class="mw-collapsible' .. expandSymbolState .. '" id="mw-customcollapsible-expandSymbol' .. id .. '">'
                .. '<div class="mw-collapsible-content mw-customtoggle-expandSymbol' .. id .. '">'
                .. '<span class="mw-customtoggle-myClade' .. id 
                ..             ' mw-customtoggle-collapseSymbol' .. id 
                ..             ' mw-customtoggle-expandSymbol' .. id 
                ..    '" style="font-size:100%;">' .. expandSymbol .. '</span>'
                .. '</div></div></td>'
    
    -- collapsible element containing the clade content 
    local contentString = '<td style="padding:0;">'
                .. '<div class="mw-collapsible' .. contentState .. '" id="mw-customcollapsible-myClade' .. id .. '>'
                .. '<div class="mw-collapsible-content mw-customtoggle-NOT_ON_CONTENT" >' -- don't toggle on the content
                
                .. '\n' .. p.main(frame)  -- important to start wikitext tables on new line
                .. '</div></div></td>'
    
    -- collapsible element containing the collapse sympol and/or text
    local collapseSymbolString = '<td style="padding:0 0 0.4em 0;">'
                .. '<div class="mw-collapsible' .. collapseSymbolState .. '" id="mw-customcollapsible-collapseSymbol' .. id .. '">'
                .. '<div class="mw-collapsible-content mw-customtoggle-collapseSymbol' .. id .. '" >'
                .. '<span class="mw-customtoggle-expandSymbol' .. id 
                            .. ' mw-customtoggle-myClade' .. id 
                            .. ' mw-customtoggle-collapseSymbol' .. id 
                            .. ' " style="font-size:100%;" >' .. collapseSymbol .. '</span>'
                            .. '</div></div></td>'


	local tableStyle = frame.args.style or ""
	if tableStyle == '{{{style}}}' then tableStyle = "" end
 	local cladeString = '<table style="border-spacing:0;margin:0;'..tableStyle ..'"><tr>'
    cladeString = cladeString .. expandSymbolString 
    if mode == "left" then
    	cladeString = cladeString .. collapseSymbolString
    end
    cladeString = cladeString .. contentString 
    if mode == "right" then
    	cladeString = cladeString .. collapseSymbolString
    end
    -- Note: if we want collapse string left and right it needs an extra element with a different id
    cladeString = cladeString ..  '</tr></table>'


return cladeString
end
------------------------------------------------------------------------------------------

--[[function getCladeTreeInfo()
	    this preprocessing loop gets information about the whole structure (number of nodes, leaves etc)
		it makes a redundant calls to the templates through transclusion, but doen't affect the template depths; 
		it provides the global lastNode that is used to limit the main while loop
--]]
function p.getCladeTreeInfo()

    -- enable proprocessing loop
    local childNumber = 0
    local childCount =0
    local maxChildren =20
    
    --info veriables (these are global for now)
    nodeCount=0
    cladeCount=0
    leafCount=0
    
	while 	childNumber < maxChildren do -- preprocessing loop
		childNumber = childNumber + 1 -- so we start with 1
		local nodeLeaf,data = mw.getCurrentFrame():getParent().args[tostring(childNumber)] or ""  -- get data from |N=
        local newickString = mw.getCurrentFrame():getParent().args['newick'..tostring(childNumber)] or ""  -- get data from |labelN=
		if newickString ~= "" or nodeLeaf ~= "" then
		--if nodeLeaf ~= "" then 
			childCount = childCount + 1  -- this counts child elements in this clade 
		    --[[]
		    for i in string.gmatch(nodeLeaf, "||rowspan") do -- count number of rows started (transclusion)
   				nodeCount = nodeCount + 1
     		end
		    for i in string.gmatch(nodeLeaf, '{|class="clade"') do -- count number of tables started (transclusion)
   				cladeCount = cladeCount + 1
     		end
     		]]
     		-- count occurences of clade structure using number of classes used and add to counters
            local _, nClades = string.gsub(nodeLeaf, 'class="clade"', "") 
            local _, nNodes = string.gsub(nodeLeaf, 'class="clade%-leaf"', "")
            cladeCount = cladeCount + nClades
            nodeCount = nodeCount  + nNodes
            
			lastNode = childNumber -- this gets the last node with a valid entry, even when missing numbers
		end
	end
--]]	
    -- nodes can be either terminal leaves or a clade structure (table)
    --    note: should change class clade-leaf to clade-node to reflect this
    nodeCount = nodeCount            -- number of nodes (class clade-leaf) passed down by transduction 
                    + childCount + 1 --  plus one for current clade and one for each of its child element
	cladeCount = cladeCount + 1       -- number of clade structure tables passed down by transduction (plus one for current clade)
	leafCount = nodeCount-cladeCount   -- number of terminal leaves (equals height of cladogram)
	
	-- output for testing: number of clades / total nodes / terminal nodes (=leaves)
	--                     (internal nodes)                   (cladogram height)
	infoOutput = '<small>[' .. cladeCount .. '/' .. nodeCount .. '/' .. leafCount .. ']</small>'
	
	return infoOutput 
	
end

--[[ code for placing TemplateStyles from the module
     source: Anomie (CC-0)  https://phabricator.wikimedia.org/T200442
]]

function p.templateStyle( frame, src )
   return frame:extensionTag( 'templatestyles', '', { src = src } );
end

function p.showClade(frame)
	--local code = frame.args.code or ""
    local code = frame:getParent().args['code2'] or ""
	
	--return  code 
	--return mw.text.unstrip(code)
	
	--local test = "<pre>Hello</pre>"
	--return string.sub(test,6,-7)
	
	local o1 =frame:getParent():getArgument('code2')
	return o1:expand()
	
	--return string.sub(code,2,-1)              -- strip marker  \127'"`UNIQ--tagname-8 hex digits-QINU`"'\127
	--return frame:preprocess(string.sub(code,3))
end
function p.testTable(frame)
	
	-- build HTML table
	local galleryTable = mw.html.create('table')
	galleryTable:addClass('wikitable')
	galleryTable:addClass('sortable')
	galleryTable:css('text-align', 'center')
	
    galleryTable:tag('caption'):wikitext('Table created with mw.html')
    
    local row = galleryTable:tag('tr')
	row:tag('th'):wikitext('Header 1')
	row:tag('th'):wikitext('Header 2')
	row:tag('th'):wikitext('Header 3')
	--:attr('rowspan', 2)
	
	row = galleryTable:tag('tr')
	row:tag('td'):css('text-align', 'left'):wikitext('A1')
	row:tag('td'):css('text-align', 'left'):wikitext('B1')
	row:tag('td'):css('text-align', 'left'):wikitext('C1')

	local row2 = mw.html.create('tr')                         -- create an independent row element
	row2:tag('td'):css('text-align', 'left'):wikitext('A3')
	row2:tag('td'):css('text-align', 'left'):wikitext('B3')
	row2:tag('td'):css('text-align', 'left'):wikitext('C3')
	
	row = galleryTable:tag('tr')
	row:tag('td'):css('text-align', 'left'):wikitext('A2')
	row:tag('td'):css('text-align', 'left'):wikitext('B2')
	row:tag('td'):css('text-align', 'left'):wikitext('C2')
	
	galleryTable:node(row2)                                   -- now add row2 as node
	
	return tostring(galleryTable)
end

function p.example(frame)
	
	local params = mw.getCurrentFrame():getParent().args
		-- build HTML table
	local exampleTable = mw.html.create('table')
	exampleTable:addClass('wikitable')
	local align = params['align'] or 'center'
	if align == 'center' then
		exampleTable:css('margin-left', 'auto')
		exampleTable:css('margin-right','auto')
	elseif align == 'right' then
		exampleTable:css('float',align)
	end
	
	local columns = {'description','code','output','comment'}
	--local headers = {'Description','Code','Output','Comment'}
    -- create header row and add requested headers
    local row = exampleTable:tag('tr')
    local headerText
    for k,v in pairs(columns) do
		if params[v] then 
			if params[v]~='' then headerText=params[v]  else headerText = firstToUpper(v) end
			row:tag('th'):wikitext(headerText) 
	    end
    end

	-- now deal with the data rows
	local i=0
	while i<10 do
		i=i+1 
		local moreRows = false
		for k,v in pairs(columns) do
			if params[v..i] then moreRows = true end
		end
		if not moreRows then break end
			
		row = exampleTable:tag('tr')
		for k,v in pairs(columns) do
			if params[v] then
				if params[v] then 
					row:tag('td'):css('text-align', 'left'):wikitext(params[v..i]) 
				else
					row:tag('td')
				end
			end
		end
    end

	return tostring(exampleTable)
end
function firstToUpper(str)
    return (str:gsub("^%l", string.upper))
end

-- display a gallery of cladograms using floating div elements

function p.gallery(frame)
	
	local params = mw.getCurrentFrame():getParent().args

    local outerBorders = false
    if params['caption'] or params['footer'] then outerBorders = true end
    
	-- the gallery container
	local gallery = mw.html.create('div'):addClass('clade-gallery'):css('float',params['align'])

	-- main caption for gallery if there is one
	
	if params['main-caption'] then
		gallery:tag('div'):wikitext(params['main-caption']):addClass('main-caption')
	end

	-- a gallery of floating divs with content

    local shading = ''
	--if params['shading'] then shading = 'shading'	end -- class for shading
	if params['shading'] then shading = 'wikitable'	end -- class for shading
	
	local vertical = '' -- defaults to horizontal
	if params['vertical'] then vertical = 'vertical'	end -- class for vertical display
	 
	local sizeStyling = ''
	if params['width'] then sizeStyling = sizeStyling .. 'width:' .. params['width'] .. ';' end
	if params['height'] then sizeStyling = sizeStyling .. 'height:' .. params['height'] .. ';' end

    local noborder = ''
    if 	not params['main-borders'] then noborder = 'no-border' end
    	
    -- the gallery	contents
    local content = gallery:tag('div'):addClass('main-content'):addClass(noborder)
                                      :css('height',params['height'])  
   	local i=0
	while i<15 do
		i=i+1 
		local caption = params['caption'..i] --or ''
		local header = params['header'..i] --or ''
		local footer = params['footer'..i] --or ''
		
		-- for each gallery element containing  a cladogram
        if params['cladogram'..i] then
		    local cladogramTable = content:tag('table')
		                  :addClass(shading):addClass('gallery-element'):addClass(vertical)
		                  :css('width',params['width'])    
		                  :css('height',params['height'])    
            if caption then 
	        	cladogramTable:tag('caption'):wikitext(caption):addClass('caption')
	        end
	        if header then 
	        	local row = cladogramTable:tag('tr'):addClass('header')
	        	row:tag('th'):wikitext(header)
	        end
	        -- if params['cladogram'..i] already checked
	        local row = cladogramTable:tag('tr')
	        row:tag('td'):wikitext(params['cladogram'..i])
	                      :addClass('cladogram')
	                      --:css("padding", "20px")
		                  :css('width',params['cladogram-width'])    
		                  :css('height',params['cladogram-height'])    
	        
	        if footer then
	        	local row = cladogramTable:tag('tr')
	        	row:tag('td'):wikitext(footer):addClass('footer'):addClass(shading)
	        end
		    
		    --[[DIV VERSION - different browsers behave differently
		    -- the div containing each element in the gallery
		    local div = content:tag('div'):addClass('gallery-element'):addClass(direction)
		                  :css('width',params['width'])    
		                  :css('height',params['height'])    
		    
		    -- the caption, header, content and footer for each element
		    if caption then div:tag('div'):wikitext(caption):addClass('caption'):addClass(shading) end
		    
		    if header then div:tag('div'):wikitext(header):addClass('header'):addClass(shading) end
		    
		    div:tag('div'):wikitext(params['cladogram'..i]):addClass('cladogram'):addClass(shading)
		                  :css('width',params['cladogram-width'])    
		                  :css('height',params['cladogram-height'])    
		    
		    if footer then div:tag('div'):wikitext(footer):addClass('footer'):addClass(shading) end
		    END DIV VERSION--]] 
	    else
	       break	
	    end
	end
	
	-- main footer
	if params['main-footer'] then
		gallery:tag('div'):wikitext(params['main-footer']):addClass('main-footer'):addClass(noborder)
	end
	-- TemplateStyles file
	local src = "Template:Clade gallery/styles.css"
    	
	return p.templateStyle( frame, src ) ..  tostring(gallery)
end

-- this must be at end
return p