Module:Clade
From Summertime Saga Wiki
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 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 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 .. ' ' -- 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 -- could replace hyphen with non-breaking hyphen (‑) 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 "⊞" 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