Module:Clade/transclude

require('strict')

local DEBUG=false

--DEBUG=true -- comment out or not runtime or debug

local p ={}

local pargs ={}

p.main = function(frame) -- called from template

pargs = frame:getParent().args

local output

local selectedTree -- subtree extracted from page content

local modifiedTree -- subtree after pruning and grafting

-- (1) get page

local page = pargs['page'] or frame.args['page']

if not page then

return p.errorMsg("Target page not provided")

end

-- (2) get content of page (move from _section(), _label, etc)

local content

local title = mw.title.new( mw.text.trim(page)) -- , ns) -- creates object if page doesn't exist (and valid page name)

--TODO: could use mw.title.makeTitle(), but that needs ns

if title then

if title.exists then

content = title:getContent()

if not content then return p.errorMsg("Content of " .. page .. " not loaded.") end

else

return p.errorMsg('Page with title "' .. page .. '" not found.')

end

end

-- (3) select from content

local section = pargs['section'] or pargs['section1'] or pargs[1]

if section then

selectedTree = p._section(frame, content, section)

end

local label = pargs['label'] or pargs['label1'] or pargs[1]

if label then

selectedTree = p._label(frame, content, label)

end

--TODO does this need to be separate from label?

local subtree = pargs['subtree'] or pargs['subtree1'] or pargs[1]

if subtree then

selectedTree = p._label(frame, content, subtree)

end

if not selectedTree then -- if none of options retrieve anything

p.errorMsg("Nothing retrieved for selection option " .. (label or subtree or section or "none"))

end

if DEBUG then return selectedTree end --- returns the code captured without processing

--(4) modify content (excise and replace; prune and graft)

local exclude = pargs['exclude'] or pargs['exclude1']

if exclude then

if pargs['exclude'] then pargs['exclude1'] = pargs['exclude'] end

if pargs['replace'] then pargs['replace1'] = pargs['replace'] end

modifiedTree = selectedTree

local i = 1

while pargs['exclude'..i] do

local exclude = pargs['exclude'..i]

local replace = pargs['replace'..i] or " " -- must be something

modifiedTree = p._xlabel(frame, modifiedTree, exclude, replace)

i=i+1

end

else

modifiedTree = selectedTree

end

--(5) other options

----- suppress hidden elements

if pargs['nohidden'] then

modifiedTree = modifiedTree:gsub("lade hidden", "lade")

end

----- suppress authorities (or anything in small tags)

if pargs['noauthority'] then

modifiedTree = modifiedTree:gsub(".-", "")

end

----- suppress images

if pargs['noimages'] then

modifiedTree = modifiedTree:gsub("%[%[File:.-%]%]", "")

end

----- wrap in outer clade

local wrap = pargs['wrap']

if wrap and (label or subtree) then

local label1 = label or string.lower(subtree)

local styleString = ""

if pargs['style'] then styleString = '|style=' .. pargs['style'] end

if wrap ~= "" then label1 = wrap end

output = "{{clade " .. styleString .. " |label1=" .. p.firstToUpper(label1) .. "|1=" .. modifiedTree .. " }}" -- last space before double brace important

else

output = modifiedTree

end

--(6) return final tree

if output then

if pargs['raw'] then

return output

else

return frame:preprocess(output)

end

end

return p.errorMsg("No valid option for transclusion")

end

--=============================== extract LABELS or SUBTREES=======================================

p.label = function (frame, page, ...)

local page = frame.args[1] --"User:Jts1882/sandbox/test/Passeriformes"

local label = frame.args[1] or frame.args['label'] or frame.args['label1']

local wrap = frame.args['wrap']

local output = p._label (frame, page, frame.args[2], frame.args[3], frame.args[4], frame.args[5] )

if wrap then

local label1 = string.lower(frame.args[2])

if wrap ~= "" then label1 = wrap end

output = "{{clade |label1=" .. p.firstToUpper(label1) .. "|1=" .. output .. "}}"

end

return frame:preprocess(output)

end

p._label = function (frame, content, ... )

-- local page = "User:Jts1882/sandbox/test/Passeriformes"

-- local label = frame.args[1] or frame.args['label']

local args = { ... }

local output = ""

if not args[1] then return p.errorMsg ("Label name not provided") end

local mode = "label"

local targetType = "label(%d)" -- standard label of form |labelN= (captures N)

local cladePrefix = "(%d)" -- standard node of form |N= (captures N)

for k,v in pairs(args) do

local section = mw.text.trim(v)

if string.upper( section) == section then

mode = "subtree"

targetType = "target(%u)" -- targets of form targetX (X=uppercase letter)

cladePrefix = "subclade(%u)" -- subclades of form subcladeX (captures X)

end

--[=[ the pattern to capture is one of two forms: labelN=Name |N={...}

targetX=NAME |subcladeX={...}

labelN = name |N = {...}

or targetX = name |subcladeX = {...}

]=]

local pattern = targetType.."=[%s%p]*"..section .. "[%s%p]+.-"..cladePrefix.."=.-(%b{})"

-- this .- skips section tags before {{clade ...}}

-- this .- skips |sublabel and styling following the label (but can return wrong clade when a subtree)

local index1, index2, selectedTree = string.match( content , pattern )

-- note index1 and index2 should match (X=X or N=N)

if selectedTree then

--[[ the tree can contain markers for subtrees like {FABIDS}

when the form is |N={FABIDS} we want to substitute the subtree

but not when the form is |targetX={FABIDS}

]]

local pattern2 = "({%u-})" -- this captures both |N={FABIDS} and |targetX={FABIDS}

-- we only want to substitute a subtree in the first kind

-- will exclude second with pattern3 test below

if string.find(selectedTree, pattern2 ) then -- if a subtree that hasn't been substituted.

--local i,j,target = string.find(value, pattern2) -- only one subtree

local i=0

for bracedMarker in string.gmatch( selectedTree , pattern2 ) do

i=i+1

-- bracedMarker is either a marker in the tree or part of following

-- targetX={bracedMarker} ... |subcladeX=s then

local pattern3 = "target(%u)=[%s]*"..bracedMarker

--?? if selectedTree == bracedMarker

if not string.find(selectedTree, pattern3 ) then

local subtree = p._label (frame, content, bracedMarker)

if subtree then

--[[ method 1: the subtree code is substituted into main tree

this substitutes the subtree within the clade structure before processing

thus there will be a problem with large trees exceeding the expansion depth

however, they can be pruned before processing

]]

--disable method 1 selectedTree = string.gsub(selectedTree, bracedMarker, subtree, 1)

--[[method 2: add the subtree code before the final double brace

substitute "|targetX={FABIDS} |subcladeX=subtree" before last double brace of selectedTree

use capture in pattern3 to find X

]]

local i,j,X = string.find(content, pattern3)

if selectedTree == bracedMarker then

selectedTree = subtree

else

selectedTree = selectedTree:sub(1,-3) -- trim final double brace

.. "\n|target" .. X .. "=" .. bracedMarker

.. "\n|subclade" .. X .. "=" .. subtree .. ""

.. "\n }}"

end

end

end --substitution of subtree

end

end

output = output .. selectedTree

else

output = output .. p.errorMsg ("Failed to capture subclade with " .. mode .. " " ..section)

end

end

if output ~= "" then

return output -- preprocess moved to entry function

else

return 'Section for label not found'

end

end

--================================== exclude LABEL ================================================

p.xlabel = function (frame, page, ...)

local page = frame.args[1] --"User:Jts1882/sandbox/test/Passeriformes"

local label = frame.args[1] or frame.args['label'] or frame.args['label1']

-- page , target tree, subtrees to exclude ...

-- page, include clade, multple clades to exclude |

return p._xlabel (frame, page, frame.args[2], frame.args[3], frame.args[4], frame.args[5])

end

p._xlabel = function (frame, targetTree, exclude, replace)

local fullOutput = targetTree

--local fullOutput = p._section(frame, page, target)

local output=targetTree -- return unmodified tree if nothing happens

local section = exclude

local targetType = "label%d"

local cladePrefix = "%d"

if string.upper( section) == section then

targetType = "target%u" -- by convention subtrees must be uppercase

cladePrefix = "subclade%u"

end

-- label = name |n= {...}

local pattern = "("..targetType.."=[%s%p]*"..section .. "[%s%p]*.-"..cladePrefix.."=.-)(%b{})"

-- ^^ this .- skips section tags before clade

-- ^^this .- skips |sublabel and styling following the label (but can return wrong clade when a subtree)

local value = string.match( fullOutput , pattern )

if value then

local trimmedTree, matches = string.gsub(fullOutput, pattern, "%1"..replace)--replaces pattern with capture %1

return trimmedTree

else

local message = ""

if string.upper(section) == section then

message = "; subtree may have been substituted, try label"

end

output = output .. p.warningMsg ("Failed to capture subclade for exclusion with label "..section..message)

end

if output ~= "" then

return output .. 'Nothing pruned'

--return frame:preprocess(fullOutput)

else

return 'Section for label not found' -- shouldn't get here

end

end

--======================================== SECTION ==================================

p.section = function (frame)

-------------------------target page ---- sections

return frame:preprocess(p._section(frame, mw.text.trim(frame.args[1]),frame.args[2],frame.args[3],frame.args[4],frame.args[5]))

end

p._section = function (frame,content,...)

local args = { ... }

local output = ""

for k,v in pairs(args) do

local section = mw.text.trim(v)

--[[ note: using the non-greedy - in (.-) to allow capture of several sections

this allows internal clade structures to be closed without capturing sisters clades

e.g. see section Tyranni in User:Jts1882/sandbox/test/Passeriformes

]]

local pattern = "

(.-)
"

for value in string.gmatch( content , pattern ) do

if value then

if frame.args.wrap or frame:getParent().args.wrap then

local label1 = frame.args.wrap or frame:getParent().args.wrap

if label1 == "" then label1 = section end

value = "{{clade |label1=" .. label1 .. "|1=" .. value .. "}}"

end

output = output .. value

end

end

end

if pargs['norefs'] or pargs['noref'] then -- strip out references

--output = mw.text.killMarkers( output )

if output:find("

output = output:gsub('', "")

output = output:gsub("", "") -- %C works, %w%p%s%c doesn't

end

end

if output ~= "" then

--return frame:preprocess(output)

return output -- leave preprocessing for entry function

else

return 'Section not found'

end

end

p.xsection = function (frame)

local page = frame.args[1] --"User:Jts1882/sandbox/test/Passeriformes"

local label = frame.args[1] or frame.args['label'] or frame.args['label1']

-- page , target tree, sections to exclude ...

return frame:preprocess(p._xsection(frame, page ,frame.args[2],frame.args[3],frame.args[4],frame.args[5]))

end

p._xsection = function (frame,page, target, ...)

local args = { ... }

local output = ""

local title = mw.title.new( page) -- , ns) -- creates object if page doesn't exist (and valid page name)

--TODO: could use mw.title.makeTitle(), but that needs ns

if title and title.exists then

local content = title:getContent()

local fullOutput = p._section(frame, page, target)

output=fullOutput

for k,v in pairs(args) do

local section = mw.text.trim(v)

--[[ note: using the non-greedy - in (.-) to allow capture of several sections

this allows internal clade structures to be closed without capturing sisters clades

e.g. see section Tyranni in User:Jts1882/sandbox/test/Passeriformes

]]

local pattern = "(

)(.-)(
)"

local value = string.match( fullOutput , pattern )

if value then

local trimmedTree, matches = string.gsub(fullOutput, pattern, "replacement string")--replaces pattern with capture %1

output = output .. trimmedTree

output = output .. "

" .. trimmedTree .. "
"

fullOutput = trimmedTree

else

output = output .. p.errorMsg ("Failed to capture subclade with label "..section)

end

end

else

return 'No page title found'

end

if output ~= "" then

--return frame:preprocess(output)

return output -- leave preprocessing for entry function

else

return 'Section not found'

end

end

function p.firstToUpper(str)

return (str:gsub("^%l", string.upper))

end

p.errorMsg = function (message)

return '' .. message .. ''

end

p.warningMsg = function (message)

return '' .. message .. ''

end

return p