Jump to content
Wikipedia The Free Encyclopedia

Module:Build bracket/Render

From Wikipedia, the free encyclopedia
Module documentationcreate purge
You might want to create a documentation page for this Scribunto module.
Editors can experiment in this module's sandbox (edit | diff) and testcases (create) pages.
Add categories to the /doc subpage. Subpages of this module.
 localRender={}

 -- ================================
 -- 1) MODULE STATE & BIND INJECTS
 -- ================================
 -- Upvalues bound in buildTable(...)
 localstate,config,Helpers,StateChecks

 -- Local stdlib aliases
 localt_insert=table.insert

 -- Helper/function locals (set during bind)
 localisempty,notempty,cellBorder,unboldParenthetical
 localshowSeeds,teamLegs,roundIsEmpty,defaultHeaderText,isBlankEntry

 -- =========================
 -- 2) CORE <td> CONSTRUCTOR
 -- =========================
 localfunctionCell(tbl,j,i,opts)
 opts=optsor{}
 localcell=tbl:tag("td")

 -- classes/attributes
 ifopts.classesthen
 for_,cinipairs(opts.classes)do
 cell:addClass(c)
 end
 end
 ifopts.colspanandopts.colspan~=1then
 cell:attr("colspan",opts.colspan)
 end
 ifopts.rowspanandopts.rowspan~=1then
 cell:attr("rowspan",opts.rowspan)
 end

 -- styling
 ifopts.borderWidththen
 cell:css("border-width",cellBorder(opts.borderWidth))
 end
 ifopts.weight=="bold"then
 cell:css("font-weight","bold")
 end
 ifopts.bgthen
 cell:css("background",opts.bg)
 end
 ifopts.colorthen
 cell:css("color",opts.color)
 end

 -- alignment helpers
 ifopts.align=="center"then
 cell:addClass("brk-center")
 elseifopts.align=="right"then
 cell:css("text-align","right")
 end

 ifopts.textthen
 cell:wikitext(opts.text)
 end
 returncell
 end

 -- =====================================
 -- 3) ENTRY SIZING (computed per-round)
 -- =====================================
 localentryColspan=nil
 localfunctiongetEntryColspan(j)
 returnentryColspanandentryColspan[j]or1
 end

 -- ==========================
 -- 4) TEAM / SCORE CELLS
 -- ==========================
 localfunctionteamCell(tbl,k,j,i,l,colspan)
 localclasses={"brk-td","brk-b",(k=="seed")and"brk-bgD"or"brk-bgL"}
 ifk=="seed"ork=="score"then
 classes[#classes+1]="brk-center"
 end

 -- strict bolding
 localweightFlag
 ifk=="team"then
 ifstate.entries[j][i].weight=="bold"then
 weightFlag="bold"
 end
 elseifk=="score"andl~=nilthen
 localsc=state.entries[j][i].score
 ifscandsc.weightandsc.weight[l]=="bold"then
 weightFlag="bold"
 end
 end

 locallegs=teamLegs(j,i)
 localopts={
 classes=classes,
 colspan=colspan,
 rowspan=2,
 borderWidth={0,0,1,1},
 weight=weightFlag-- 'bold' or nil
 }

 -- borders
 ifk=="team"andlegs==0then
 opts.borderWidth[2]=1
 end
 ifstate.entries[j][i].position=="top"then
 opts.borderWidth[1]=1
 end
 ifl==legsorl=="agg"ork=="seed"then
 opts.borderWidth[2]=1
 end

 -- text
 localfunctiontostr(x)
 return(x==nil)and""ortostring(x)
 end
 ifl==nilthen
 opts.text=unboldParenthetical(tostr(state.entries[j][i][k]))
 else
 localv=state.entries[j][i][k]andstate.entries[j][i][k][l]
 opts.text=tostr(v)
 end

 -- ensure seeds inherit team bold without affecting score logic
 ifk=="seed"andstate.entries[j][i]andstate.entries[j][i].weight=="bold"then
 opts.weight=opts.weightor"bold"
 end
 returnCell(tbl,j,i,opts)
 end

 -- ======================================
 -- 5) NIL/BLANK ENTRY HANDLING PER CELL
 -- ======================================
 localfunctionhandleEmptyOrNilEntry(tbl,j,i,R)
 localentry_colspan=getEntryColspan(j)
 localcol=state.entries[j]or{}

 -- nil entry: optionally emit spanning blank to keep grid intact
 ifcol[i]==nilthen
 ifcol[i-1]~=nilori==1then
 localrowspan,row=0,i
 repeat
 rowspan=rowspan+1
 row=row+1
 untilcol[row]~=nilorrow>R
 Cell(tbl,j,i,{rowspan=rowspan,colspan=entry_colspan})
 returntrue
 else
 returntrue-- intentionally omitted cell
 end
 end

 ifcol[i]["ctype"]=="blank"then
 returntrue
 end
 returnfalse
 end

 -- ============================
 -- 6) ENTRY INSERTORS (ctype)
 -- ============================
 -- 6.1 Header
 localfunctioninsertHeader(tbl,j,i,entry)
 localbyesJ=state.byes[j]
 localhideJ=state.hide[j]
 localentry_colspan=getEntryColspan(j)

 if(byesJandbyesJ[entry.headerindex]androundIsEmpty(j,i))or(hideJandhideJ[entry.headerindex])then
 returnCell(tbl,j,i,{rowspan=2,colspan=entry_colspan})
 end

 ifisempty(entry.header)then
 entry.header=defaultHeaderText(j,entry.headerindex)
 end

 localclasses={"brk-td","brk-b","brk-center"}
 localuseCustomShade=entry.shade_is_rdandnotisempty(entry.shade)
 ifnotuseCustomShadethen
 t_insert(classes,"brk-bgD")
 end

 localcellOpts={
 rowspan=2,
 colspan=entry_colspan,
 text=entry.header,
 classes=classes,
 borderWidth={1,1,1,1}
 }
 ifuseCustomShadethen
 cellOpts.bg=entry.shade
 end
 returnCell(tbl,j,i,cellOpts)
 end

 -- 6.2 Team (+seed/+scores/+agg)
 localfunctioninsertTeam(tbl,j,i,entry)
 localbyesJ=state.byes[j]
 localhideJ=state.hide[j]
 localentry_colspan=getEntryColspan(j)
 localmaxlegs=state.maxlegs[j]or1
 locallegs=teamLegs(j,i)
 localteam_colspan=maxlegs-legs+1

 -- bye/hidden → reserve footprint
 if((byesJandbyesJ[entry.headerindex])andisBlankEntry(j,i))or(hideJandhideJ[entry.headerindex])then
 returnCell(tbl,j,i,{rowspan=2,colspan=entry_colspan})
 end

 ifconfig.aggregateandlegs==1andmaxlegs>1then
 team_colspan=team_colspan+1
 end
 ifmaxlegs==0then
 team_colspan=team_colspan+1
 end

 -- seed
 ifconfig.seedsthen
 ifshowSeeds(j,i)==truethen
 teamCell(tbl,"seed",j,i)
 else
 team_colspan=team_colspan+1
 end
 end

 -- team name
 teamCell(tbl,"team",j,i,nil,team_colspan)

 -- scores
 forl=1,legsdo
 teamCell(tbl,"score",j,i,l)
 end

 -- aggregate
 ifconfig.aggregateandlegs>1then
 teamCell(tbl,"score",j,i,"agg")
 end
 end

 -- 6.3 Text
 localfunctioninsertText(tbl,j,i,entry)
 Cell(tbl,j,i,{rowspan=2,colspan=getEntryColspan(j),text=entry.text})
 end

 -- 6.4 Group (spans columns)
 localfunctioninsertGroup(tbl,j,i,entry)
 localspan=state.entries[j][i].colspanor1
 localcolspan=0

 -- sum entry widths per column
 form=j,j+span-1do
 colspan=colspan+state.maxlegs[m]+2
 ifnotconfig.seedsthen
 colspan=colspan-1
 end
 if(config.aggregateandstate.maxlegs[m]>1)orstate.maxlegs[m]==0then
 colspan=colspan+1
 end
 end

 -- add path columns between rounds
 form=j,j+span-2do
 colspan=colspan+(state.hascross[m]and3or2)
 end

 returnCell(tbl,j,i,{rowspan=2,colspan=colspan,classes={"brk-center"},text=entry.groupor""})
 end

 -- 6.5 Line
 localfunctioninsertLine(tbl,j,i,entry)
 localentry_colspan=getEntryColspan(j)

 localborderWidth={0,0,0,0}
 ifentry.borderWidththen
 borderWidth=entry.borderWidth
 else
 -- derive from left path column
 localwantTop=entry.border=="top"orentry.border=="both"
 localwantBottom=(entry.border==nil)orentry.border=="bottom"orentry.border=="both"

 ifwantBottomandstate.pathCell[j-1]andstate.pathCell[j-1][i+1]then
 borderWidth[3]=2*(state.pathCell[j-1][i+1][3][1][3]or0)
 end
 ifwantTopandstate.pathCell[j-1]andstate.pathCell[j-1][i]then
 borderWidth[1]=2*(state.pathCell[j-1][i][3][1][1]or0)
 end
 end

 localcell=Cell(tbl,j,i,{rowspan=2,colspan=entry_colspan,text=entry.text,borderWidth=borderWidth})
 cell:addClass("brk-line")
 ifentry.colorthen
 cell:css("border-color",entry.color)
 end
 returncell
 end

 localINSERTORS={
 header=insertHeader,
 team=insertTeam,
 text=insertText,
 group=insertGroup,
 line=insertLine
 }

 localfunctioninsertEntry(tbl,j,i,R)
 ifhandleEmptyOrNilEntry(tbl,j,i,R)then
 return
 end
 localentry=state.entries[j][i]
 ifnotentrythen
 return
 end
 localfn=INSERTORS[entry.ctype]
 iffnthen
 returnfn(tbl,j,i,entry)
 end
 returnCell(tbl,j,i,{rowspan=2,colspan=getEntryColspan(j)})
 end

 -- ===================================
 -- 7) PATH CELL EMITTERS (between RDs)
 -- ===================================
 -- Always emit the correct number of <td>s:
 -- - 2 lanes when no cross (k=1,3)
 -- - 3 lanes when cross (k=1,2,3). The center lane (k=2) is skipped only if no cross.
 localfunctiongeneratePathCell(tbl,j,i,k,bg,rowspan)
 ifnotstate.hascross[j]andk==2then
 return
 end-- keep table aligned

 localcolData=state.pathCell[j][i][k]
 localborders=(colDataandcolData[1])or{0,0,0,0}
 localcolor=(colDataandcolData.color)or"transparent"

 localcell=tbl:tag("td")
 ifrowspanandrowspan~=1then
 cell:attr("rowspan",rowspan)
 end

 ifk==2andstate.hascross[j]andnotempty(bg)then
 cell:css("background",bg):css("transform","translate(-1px)")
 end

 ifborders[1]~=0orborders[2]~=0orborders[3]~=0orborders[4]~=0then
 cell:css("border","solid "..color):css(
 "border-width",
 (2*borders[1])..
 "px "..(2*borders[2]).."px "..(2*borders[3]).."px "..(2*borders[4]).."px"
 )
 end
 returncell
 end

 localfunctioninsertPath(tbl,j,i,R)
 ifstate.skipPath[j][i]then
 return
 end

 localcolspan,rowspan=2,1
 localbg=""
 localcross={"",""}

 localPj=state.pathCell[j]
 localXj=state.crossCell[j]
 localSPj=state.skipPath[j]

 -- vertical merge: extend rowspan down while borders repeat identically
 ifi<Rthen
 localfunctionsameBorders(a)
 ifa>R-1orSPj[a]then
 returnfalse
 end
 localpi,pa=Pj[i],Pj[a]
 fork=1,3do
 localbi,ba=pi[k][1],pa[k][1]
 ifbi[1]~=ba[1]orbi[2]~=ba[2]orbi[3]~=ba[3]orbi[4]~=ba[4]then
 returnfalse
 end
 end
 returntrue
 end
 ifsameBorders(i)then
 localrow=i
 repeat
 ifrow~=iandsameBorders(row)then
 SPj[row]=true
 end
 rowspan=rowspan+1
 row=row+1
 untilrow>RornotsameBorders(row)
 rowspan=rowspan-1
 end
 end

 -- avoid double-emitting cross rows (previous row already spans)
 if
 i>1andXj[i-1]and
 ((Xj[i-1].leftandXj[i-1].left[1]==1)or(Xj[i-1].rightandXj[i-1].right[1]==1))
 then
 return
 end

 -- cross visuals
 ifstate.hascross[j]then
 colspan=3
 ifXj[i].left[1]==1orXj[i].right[1]==1then
 rowspan=2
 ifXj[i].left[1]==1then
 cross[1]=
 "linear-gradient(to top right, transparent calc(50% - 1px),"..
 Xj[i].left[2]..
 " calc(50% - 1px),"..Xj[i].left[2].." calc(50% + 1px), transparent calc(50% + 1px))"
 end
 ifXj[i].right[1]==1then
 cross[2]=
 "linear-gradient(to bottom right, transparent calc(50% - 1px),"..
 Xj[i].right[2]..
 " calc(50% - 1px),"..Xj[i].right[2].." calc(50% + 1px), transparent calc(50% + 1px))"
 end
 end
 ifnotempty(cross[1])andnotempty(cross[2])then
 cross[1]=cross[1]..","
 end
 bg=cross[1]..cross[2]
 end

 -- emit L | (CENTER) | R cells
 fork=1,3do
 generatePathCell(tbl,j,i,k,bg,rowspan)
 end
 end

 -- =========================================
 -- 8) INVISIBLE SCAFFOLDING (LEGACY COMPAT)
 -- =========================================
 localfunctionemitRowHeight(tr,rowHeightPx)-- left height cell per data row
 tr:tag("td"):css("height",rowHeightPx)
 end

 -- widths row: fixes widths for seed/team/score(+agg) and path columns
 localfunctionemitWidthsRow(tbl,MINC,C,seedW,teamW,scoreW,aggW,pathW,leftPad,rightPad,crossW,crossPad)
 tbl:tag("tr"):css("visibility","collapse")-- spacer

 localtr=tbl:tag("tr")
 tr:tag("td"):css("width","1px")-- tiny leading gutter

 forj=MINC,Cdo
 ifconfig.seedsthen
 tr:tag("td"):css("width",seedW)
 end
 tr:tag("td"):css("width",teamW)

 localmaxlegs=(state.maxlegsandstate.maxlegs[j])or1
 ifmaxlegs<=0then
 tr:tag("td"):css("width",scoreW)-- legacy extra column when maxlegs == 0
 else
 for_=1,maxlegsdo
 tr:tag("td"):css("width",scoreW)
 end
 end
 ifconfig.aggregateandmaxlegs>1then
 tr:tag("td"):css("width",aggW)
 end

 ifj<Cthen
 ifstate.hascrossandstate.hascross[j]then
 tr:tag("td"):css("width",pathW):css("padding-left",leftPad)-- L
 tr:tag("td"):css("width",crossW):css("padding-left",crossPad)-- CENTER
 tr:tag("td"):css("width",pathW):css("padding-right",rightPad)-- R
 else
 tr:tag("td"):css("width",pathW):css("padding-left",leftPad)-- L
 tr:tag("td"):css("width",pathW):css("padding-right",rightPad)-- R
 end
 end
 end
 end

 -- ==============================
 -- 9) PUBLIC: BUILD THE TABLE
 -- ==============================
 functionRender.buildTable(frame,_state,_config,_Helpers,_StateChecks)
 -- Bind upvalues
 state,config,Helpers,StateChecks=_state,_config,_Helpers,_StateChecks

 -- Helpers
 isempty,notempty=Helpers.isempty,Helpers.notempty
 cellBorder=Helpers.cellBorder
 unboldParenthetical=Helpers.unboldParenthetical

 -- State checks
 showSeeds=StateChecks.showSeeds
 teamLegs=StateChecks.teamLegs
 roundIsEmpty=StateChecks.roundIsEmpty
 defaultHeaderText=StateChecks.defaultHeaderText
 isBlankEntry=StateChecks.isBlankEntry

 -- Hot locals
 localMINC,C=config.minc,config.c
 localR0=tonumber(config.r)or0
 localentries=state.entries
 localpathCell=state.pathCell
 localcrossCell=state.crossCell
 localhide=state.hideor{}
 localbyes=state.byesor{}
 localmaxlegsArr=state.maxlegsor{}
 localhascross=state.hascrossor{}

 -- Respect explicit |rows=|
 localuserRowsArg=config._fargsandconfig._fargs.rows

 -- Is this entry visibly rendered
 localfunctionentryIsVisible(j,i)
 localcol=entries[j]
 locale=colandcol[i]
 ifnotethen
 returnfalse
 end

 localct=e.ctype
 localhidx=e.headerindex
 localhid=(hide[j]andhide[j][hidx])orfalse
 ifhidthen
 returnfalse
 end

 ifct=="team"then
 localbye=(byes[j]andbyes[j][hidx])orfalse
 ifbyeandisBlankEntry(j,i)then
 returnfalse
 end
 returntrue
 elseifct=="header"then
 localbye=(byes[j]andbyes[j][hidx])orfalse
 ifbyeandroundIsEmpty(j,i)then
 returnfalse
 end
 returntrue-- header shows (defaults applied) unless hidden or bye+empty
 elseifct=="text"then
 returnnotempty(e.text)
 elseifct=="group"then
 returnnotempty(e.group)
 elseifct=="line"then
 -- Count visible borders via path scan, not the placeholder cell itself
 returnnotempty(e.text)-- only count if it actually prints text
 end
 returnfalse
 end

 -- Backward scan for last visually-used row (entries or painted paths)
 localfunctioncomputeBottomUsedRow(R)
 fori=R,1,-1do
 -- Any visible entry on this row?
 forj=MINC,Cdo
 ifentryIsVisible(j,i)then
 locale=entries[j][i]
 -- teams occupy a row pair → include the following row if within bounds
 ife.ctype=="team"then
 return(i+1<=R)and(i+1)ori
 else
 returni
 end
 end
 end

 -- Any path/cross on this row?
 forj=MINC,C-1do
 localPj=pathCell[j]
 localXj=crossCell[j]

 -- Cross uses a row pair
 localcc=XjandXj[i]
 ifccand((cc.leftandcc.left[1]==1)or(cc.rightandcc.right[1]==1))then
 return(i+1<=R)and(i+1)ori
 end

 -- Any nonzero border on any lane?
 localrow=PjandPj[i]
 ifrowthen
 fork=1,3do
 localcell=row[k]
 localb=cellandcell[1]
 ifband((b[1]or0)~=0or(b[2]or0)~=0or(b[3]or0)~=0or(b[4]or0)~=0)then
 returni
 end
 end
 end
 end
 end
 return1-- nothing visible; keep at least one row
 end

 -- Effective number of data rows to emit
 localR_eff=(userRowsArganduserRowsArg~="")andR0orcomputeBottomUsedRow(R0)

 -- Precompute entryColspan per round
 entryColspan={}
 forj=MINC,Cdo
 localml=maxlegsArr[j]or1
 localcol=ml+2
 ifnotconfig.seedsthen
 col=col-1
 end
 if(config.aggregateandml>1)orml==0then
 col=col+1
 end
 entryColspan[j]=col
 end

 -- Table skeleton
 localtbl=mw.html.create("table"):addClass("brk")
 ifconfig.nowrapthen
 tbl:addClass("brk-nw")
 end

 -- Fixed internal row height (do NOT use config.height here)
 localrowHeightPx="11px"

 -- Column widths (resolve once)
 localgetWidth=Helpers.getWidth
 localseedW=(getWidthandgetWidth("seed","25px"))or"25px"
 localteamW=(getWidthandgetWidth("team","150px"))or"150px"
 localscoreW=(getWidthandgetWidth("score","25px"))or"25px"
 localaggRaw=(getWidthandgetWidth("agg",nil))ornil
 localaggW=Helpers.isempty(aggRaw)andscoreWoraggRaw
 localpathW="2px"
 localcrossW=(getWidthandgetWidth("cross","5px"))or"5px"
 localcrossPad=(getWidthandgetWidth("crosspad","5px"))or"5px"

 -- between-round spacing split (e.g., 6 → 4px left, 2px right)
 localspacing=tonumber(config.colspacing)or6
 localleftFrac=math.floor(spacing*2/3)
 localleftPad,rightPad=(leftFrac.."px"),((spacing-leftFrac).."px")

 -- widths row
 emitWidthsRow(tbl,MINC,C,seedW,teamW,scoreW,aggW,pathW,leftPad,rightPad,crossW,crossPad)

 -- Data rows
 fori=1,R_effdo
 localtr=tbl:tag("tr")
 emitRowHeight(tr,rowHeightPx)-- left height cell

 forj=MINC,Cdo
 insertEntry(tr,j,i,R_eff)
 ifj<Cthen
 insertPath(tr,j,i,R_eff)
 end
 end
 end

 -- Wrap with a div that loads TemplateStyles and enables scroll overflow
 localfr=frameormw.getCurrentFrame()
 localcontainer=mw.html.create("div")

 -- Height now applies to container (numbers → px; units respected)
 localcontainerHeight=Helpers.toCssLength(config.height,nil)
 ifcontainerHeightthen
 container:css("max-height",containerHeight)
 end

 container:css("overflow-x","auto"):css("overflow-y","auto")
 container:wikitext(fr:extensionTag("templatestyles","",{src="Module:Build bracket/styles.css"}))
 container:node(tbl)

 returntostring(container)
 end

 -- ============
 -- 10) EXPORTS
 -- ============
 returnRender

AltStyle によって変換されたページ (->オリジナル) /