Module:Clade
Documentation for this module may be created at Module:Clade/doc
--[[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 if terminal clade template
add to every clade table >> 137 times in Passeriformes test page
cladeCount==1 >> 43 insertions
removing conitional addition for now as this reintroduces the extra line due to parser bug T18700
--]]
--if cladeCount==1 then
local src = "Template:Clade/styles.css"
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
newickString = p.processNewickString(newickString,childNumber)
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|}'
cladeString = p.addSubTrees(cladeString) -- add subtrees
return cladeString
--return '<div style="width:auto;">\n' .. cladeString .. '</div>'
end
--[[ =============================function to add subtrees ========================================== ]]
function p.addSubTrees(cladeString)
local pargs = mw.getCurrentFrame():getParent().args
local suffix = { [1]="A", [2]="B", [3]="C", [4]="D", [5]="E", [6]="F", [7]="G", [8]="H", [9]="I", [10]="J",
[11]="K", [12]="L", [13]="M", [14]="N", [15]="O", [16]="P", [17]="Q", [18]="R", [19]="S", [20]="T",
[21]="U", [22]="V", [23]="W", [24]="X", [25]="Y", [26]="Z"}
for i=1, 26, 1 do
local subclade = pargs['subclade'..suffix[i]]
local target = pargs['target'..suffix[i]] or "SUBCLADE_" .. suffix[i]
if subclade then
if string.find(cladeString, target) then
cladeString = string.gsub(cladeString,target,subclade)
end
end
end
return cladeString
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 branchLabelStyle = mw.getCurrentFrame():getParent().args['branch'..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['grouplabelstyle'..tostring(childNumber)] or
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 ~= '') or (branchLabelStyle ~= '') then
styleString = 'style="' .. borderStyle .. branchStyle .. branchLabelStyle .. '"'
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 .. ' ' -- 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/>' --' <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 ' ' --    (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," ", " ") -- replace spaces with non-breaking space
if not nowrapString:find("UNIQ.-QINU") then -- unless a strip marker
nowrapString = string.gsub(nowrapString,"-", "‑") -- replace hyphen with non-breaking hyphen (‑)
end
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'] or mw.getCurrentFrame():getParent().args['newick']
--if newickString == '{{{newickstring}}}' then return newickString end
newickString = p.processNewickString(newickString,"") -- "childNumber")
-- 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.processNewickString(newickString,childNumber)
local maxPatterns = 5
local i = 0
local pargs = mw.getCurrentFrame():getParent().args
local pattern = pargs['newick'..tostring(childNumber)..'-pattern'] -- unnumbered option for i=1
local replace = pargs['newick'..tostring(childNumber)..'-replace']
while i < maxPatterns do
i=i+1
pattern = pattern or pargs['newick'..tostring(childNumber)..'-pattern'..tostring(i)]
replace = replace or pargs['newick'..tostring(childNumber)..'-replace'..tostring(i)] or ""
if pattern then
newickString = string.gsub (newickString, pattern, replace)
end
pattern = nil; replace = nil
end
newickString = string.gsub (newickString, "_", " ") -- replace underscore with space
return 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 "⊞"
local collapseSymbol = mw.getCurrentFrame():getParent().args['collapse-symbol'] or "⊟"
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