if not modules then modules = { } end modules ['back-exp-imp-mth'] = { version = 1.001, comment = "companion to back-exp.mkiv", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files" } local sub, gsub = string.sub, string.gsub local utfchar, utfvalues = utf.char, utf.values local insert = table.insert local setmetatableindex, concat = table.setmetatableindex, table.concat local structurestags = structures.tags local specifications = structurestags.specifications local locatedtag = structurestags.locatedtag local backend = structurestags.backend local setattribute = backend.setattribute local extras = backend.extras local checks = backend.checks local finalizers = backend.finalizers ----- bpfactor = number.dimenfactors.bp ----- f_points = string.formatters["%p"] local f_em = string.formatters["%.6Nem"] local implement = interfaces.implement local mathmlns = "http://www.w3.org/1998/Math/MathML" do ----- automathrows = true directives.register("export.math.autorows", function(v) automathrows = v end) ----- automathapply = true directives.register("export.math.autoapply", function(v) automathapply = v end) ----- automathnumber = true directives.register("export.math.autonumber", function(v) automathnumber = v end) local automathstrip = true directives.register("export.math.autostrip", function(v) automathstrip = v end) local functions = mathematics.categories.functions local tagging = mathematics.tagging local functiontype = mathematics.functiontype local function collapse(di,i,data,ndata,detail,element) local collapsing = di.data if data then di.element = element di.detail = nil i = i + 1 while i <= ndata do local dn = data[i] if dn.detail == detail then collapsing[#collapsing+1] = dn.data[1] dn.skip = "ignore" i = i + 1 else break end end end return i end local function collapse_all_mn(data) local n = #data local f = false local l = false local d = false local p = false for i=1,n do local di = data[i] local tg = di.tg if tg == "mn" then if not f then d = di.data f = i p = false else if p then p.element = "mn" local s = specifications[p.fulltag] s.mathcharacter = nil s.mathclass = nil s.mathgroup = nil s.mathindex = nil end d[#d+1] = di.data[1] data[i] = false p = false end l = i elseif tg == "mi" or tg == "mc" then if p then f = false end if i < n and data[i+1].tg == "mn" then local d1 = di.data[1] if d1 then local c = d1.content local s = specifications[di.fulltag] if s.mathclass == "ordinary" and c and (c == "." or c == ",") then if not f then d = di.data f = i l = i p = di else d[#d+1] = d1 data[i] = false end else f = false end else f = false end else f = false end elseif f and (tg == "msub" or tg == "msup" or tg == "msubsup") then local d1 = di.data[1] if d1 and d1.tg == "mn" then d[#d+1] = d1.data[1] d1.data = d data[f] = false end f = false else f = false end end if d then local j = 0 for i=1,n do local d = data[i] if d then j = j + 1 data[j] = d end end for i=j+1,n do data[i] = nil end end end local functioncontent = { } setmetatableindex(functioncontent,function(t,k) local v = { { content = k } } t[k] = v return v end) local dummy_nucleus = { element = "mtext", data = { content = "" }, nature = "mixed", comment = "dummy nucleus", fulltag = "mtext>0" } local function accentchar(d) -- see mkiv for old one local detail = tonumber(d.detail) if detail then local d1 = d.data[1] if d1 and d1.tg == "mrow" then -- d.element = "mrow" -- used d.detail = nil local s = specifications[d.fulltag] -- hm, used when not set s.detail = nil -- d1.element = "mo" -- used -- d1.detail = nil d1.attributes = { stretchy = "true" } d1.nature = "mixed" d1.data = { { content = utfchar(detail) } } return d end end end local function maybetext(d) if #d.data == 1 and d.data[1].content then d.element = "mtext" else -- maybe also check if d.data[1].content d.element = "mrow" end return d end local no_mrow = { math = true, mrow = true, mfenced = true, mfrac = true, mroot = true, msqrt = true, mtable = true, mi = true, mo = true, mn = true, mspace = true, mtext = true, -- mmultiscripts = true, -- mstacker = true, -- mextensible = true, -- mdelimited = true, } -- we could make a stupid xml and then just use lpath to clean it up -- local separator = utfchar(0x2061) -- local function markapplicationof(di,data,i) -- if i > 1 then -- local d0 = data[i-1] -- local at = d0.attributes -- if at then -- at.mathapplication = true -- else -- d0.attributes = { -- mathapplication = true, -- } -- end -- end -- -- di.tg = "ignore" -- -- di.skip = "ignore" -- end local function checked(d,n) if n == 1 then local di = d[1] if di then local tg = di.tg if tg == "ignore" then -- todo: we can move ignore's data one level up return 1 elseif di.content then return 1 else local dd = di.data local nn = #dd if nn > 0 and checked(dd,nn) > 0 then return 1 else return 0 end end else -- kind of weird return 0 end else local m = 0 for i=1,n do local di = d[i] if di then local tg = di.tg if tg == "ignore" then -- skip elseif di.content then m = m + 1 d[m] = di else local dd = di.data local nn = #dd if nn > 0 and checked(dd,nn) > 0 then m = m + 1 d[m] = di end end else -- kind of weird end end if m < n then for i=n,m+1,-1 do d[i] = nil end end return m end end local stretched = { operator = "false", open = "false", close = "false", middle = "false", integral = "false", } local nbsp = utfchar(0x00A0) --   local mbsp = utfchar(0x2000) -- after : ; , -- ," ",nbsp local replacer = lpeg.replacer { [", "] = ",".. mbsp, [". "] = ".".. mbsp, [": "] = ":".. mbsp, ["; "] = ";".. mbsp, [","] = ",".. mbsp, ["."] = ".".. mbsp, [":"] = ":".. mbsp, [";"] = ";".. mbsp, [" "] = nbsp, } local function checkmath(root) -- we can provide utf.toentities as an option local data = root.data if data then local ndata = #data local roottg = root.tg if roottg == "mo" then local s = specifications[root.fulltag] local class = s.mathclass local group = s.mathgroup local delimiter = s.delimiter -- why not print(s.accent) if class or group then root.attributes = { mathclass = class, mathgroup = group, mathindex = s.mathindex, mathcharacter = s.mathcharacter, mathstack = s.mathstack, stretchy = (delimiter == "true" and "true") or stretched[class] or nil, } else -- todo: stackers, accents etc -- we can put an accent class on the char root.attributes = { mathstack = s.mathstack, -- stretchy = "false", stretchy = delimiter == "true" and "true" or "false", -- too much now } end elseif roottg == "mfenced" then local s = specifications[root.fulltag] local o = s.operator if o then root.skip = "comment" -- root.content = utfchar(o) -- use embedded for now elseif tagging.mfenced then local l = s.left local m = s.middle local r = s.right if l then l = utfchar(l) end if m then local t = { } for i=1,#m do t[i] = utfchar(m[i]) end m = concat(t) end if r then r = utfchar(r) end root.attributes = { open = l, separators = m, close = r, stretchy = "true", } else root.element = "mrow" root.attributes = { mathcategory = s.mathcategory, -- stretchy = "true", -- not needed, more a signal } end -- elseif roottg == "mstacker" then elseif roottg == "mrow" then ndata = checked(data,ndata) end if ndata == 0 then root.skip = "comment" -- get rid of weird artefacts root.nota = "weird" return elseif ndata == 1 then local d = data[1] if not d or d == "" then root.skip = "comment" return elseif d.content then return elseif roottg == "mrow" or roottg == "mtext" then -- maybe just always ! check spec first -- or we can have checks.* for each as we then can flatten local s = specifications[root.fulltag] if s.mathunit then d.attributes = { mathunit = s.mathunit } elseif s.mathdigits then d.attributes = { mathdigits = s.mathdigits } elseif s.mathfunction then d.attributes = { mathfunction = s.mathfunction, mathcategory = s.mathcategory, mathstack = s.mathstack, } elseif no_mrow[d.tg] then root.skip = "comment" else d.attributes = { mathstack = s.mathstack, } end elseif roottg == "mo" then if d.tg == "mo" then root.skip = "comment" end end end -- todo local i = 1 while i <= ndata do -- -- -- TOO MUCH NESTED CHECKING -- -- -- local di = data[i] if di and not di.content then local tg = di.tg if tg == "math" then -- di.element = "mrow" -- when properties di.skip = "comment" checkmath(di) i = i + 1 elseif tg == "mover" then local s = specifications[di.fulltag] if s.accent then local t = s.top local d = di.data -- todo: accent = "false" (for scripts like limits) di.attributes = { accent = "true", mathcategory = s.mathcategory, } -- todo: p.topfixed if t then -- mover if true then local dd = d[1].data if dd then dd[1].content = utfchar(t) end end -- di.data = { d[2], d[1] } end else -- can't happen end checkmath(di) i = i + 1 elseif tg == "munder" then local s = specifications[di.fulltag] if s.accent then local b = s.bottom local d = di.data -- todo: accent = "false" (for scripts like limits) di.attributes = { accent = "true", mathcategory = s.mathcategory, } -- todo: p.bottomfixed if b then -- munder if true then local dd = d[2].data if dd then dd[1].content = utfchar(b) end end end else -- can't happen end checkmath(di) i = i + 1 elseif tg == "munderover" then local s = specifications[di.fulltag] if s.accent then local t = s.top local b = s.bottom local d = di.data -- todo: accent = "false" (for scripts like limits) -- todo: accentunder = "false" (for scripts like limits) di.attributes = { accent = "true", accentunder = "true", } -- todo: p.topfixed -- todo: p.bottomfixed if t and b then -- munderover if true then -- we don't go here any more local dd = d[1].data if dd then dd[1].content = utfchar(t) end local dd = d[3].data if dd then dd[1].content = utfchar(b) end end di.data = { d[2], d[3], d[1] } else -- can't happen end else -- can't happen end checkmath(di) i = i + 1 elseif tg == "mstacker" then local d = di.data local d1 = d[1] local d2 = d[2] local d3 = d[3] local t1 = d1 and d1.tg local t2 = d2 and d2.tg local t3 = d3 and d3.tg local m = nil -- d1.data[1] local t = nil local b = nil -- only accent when top / bot have stretch -- normally we flush [base under over] which is better for tagged pdf if t1 == "mstackermid" then if t2 == "mstackertop" then if t3 == "mstackerbot" then m = accentchar(d1) or maybetext(d1)-- or m t = accentchar(d2) or maybetext(d2) b = accentchar(d3) or maybetext(d3) di.element = "munderover" di.data = { m or d1.data[1], b, t } else m = accentchar(d1) or maybetext(d1)-- or m t = accentchar(d2) or maybetext(d2) di.element = "mover" di.data = { m or d1.data[1], t } end elseif t2 == "mstackerbot" then if t3 == "mstackertop" then m = accentchar(d1) or maybetext(d1)-- or m b = accentchar(d2) or maybetext(d2) t = accentchar(d3) or maybetext(d3) di.element = "munderover" di.data = { m or d1.data[1], t, b } else m = accentchar(d1) or maybetext(d1)-- or m b = accentchar(d2) or maybetext(d2) di.element = "munder" di.data = { m or d1.data[1], b } end else m = accentchar(d1) -- or m di.element = "mrow" di.data = { m or d1.data[1] } end end if t or b then di.attributes = { accent = t and "true" or nil, accentunder = b and "true" or nil, } di.detail = nil end checkmath(di) i = i + 1 elseif tg == "mroot" then local data = di.data local size = #data if size == 1 then -- else firefox complains ... code in math-tag (for pdf tagging) di.element = "msqrt" elseif size == 2 then data[1], data[2] = data[2], data[1] end checkmath(di) i = i + 1 elseif tg == "mdelimited" then local d = di.data local n = #d for i=1,n do local di = d[i] local properties = specifications[di.fulltag] or { } local location = properties.delimiterlocation if location then if di.attributes then di.attributes.delimiterlocation = location else -- useless and bad anyway di.attributes = { delimiterlocation = location } end end end di.element = "mrow" checkmath(di) i = i + 1 elseif tg == "mstack" then di.element = "mover" local scriptlevel = "1" -- "+1" if di.attributes then di.attributes.scriptlevel = scriptlevel else di.attributes = { scriptlevel = scriptlevel } end checkmath(di) i = i + 1 elseif tg == "break" then di.skip = "comment" i = i + 1 elseif tg == "mspace" then -- di.empty = true local s = specifications[di.fulltag] local e = s and s.emfactor if e and e ~= 0 then di.element = "mspace" di.attributes = { width = f_em(e), } end i = i + 1 elseif tg == "mtext" then -- this is only needed for unboxed mtexts ... all kind of special -- tex border cases and optimizations ... trial and error local data = di.data local size = #data -- bmatrix ... if size > 1 then for i=1,size do local di = data[i] local content = di.content if content then data[i] = { element = "mtext", nature = "inline", data = { di }, n = 0, } -- di.content = gsub(content," ",nbsp) di.content = lpeg.match(replacer,content) elseif di.tg == "math" then local di = di.data[1] if di then data[i] = di checkmath(di) end end end di.element = "mrow" -- di.tg = "mrow" -- di.nature = "inline" elseif size > 0 then local di = data[1] local content = di.content if content then -- di.content = gsub(content," ",nbsp) di.content = lpeg.match(replacer,content) end end checkmath(di) i = i + 1 elseif tg == "mi" then local s = specifications[di.fulltag] if s.mathclass == "punctuation" then di.element = "mtext" checkmath(di) i = i + 1 else -- if di.data[1].content == separator then -- markapplicationof(di,data,i) -- end local class = s.mathclass local group = s.mathgroup -- local category = s.mathcategory if class or group then di.attributes = { mathclass = class, mathgroup = group, mathindex = s.mathindex, mathcharacter = s.mathcharacter, mathstack = s.mathstack, } else di.attributes = { mathstack = s.mathstack, } end checkmath(di) i = i + 1 end elseif tg == "mrow" then local d = di.data local n = #d local s = specifications[di.fulltag] if s.mathunit then di.attributes = { mathunit = s.mathunit } i = i + 1 elseif s.mathdigits then -- does this ever happen (if so check) for i=1,n do d[i] = d[i].data[1].content or "" end di.tg = "mn" di.element = "mn" di.nature = "mixed" di.data = { { content = concat(d) } } i = i + 1 elseif s.mathfunction then di.attributes = { mathfunction = s.mathfunction, mathstack = s.mathstack } local category = tonumber(s.mathcategory) if functiontype(category) == "function" then local fnc = functions[category] -- functions[s.mathfunction] local tag = fnc and fnc.tag if tag then di.tg = "mi" di.element = "mi" di.nature = "mixed" di.data = functioncontent[tag] end end i = i + 1 elseif s.mathfunctionstack then di.attributes = { mathfunction = s.mathfunction, mathstack = s.mathstack, mathfunctionstack = s.mathfunctionstack, } checkmath(di) i = i + 1 elseif s.mathdelimitedstack then di.attributes = { mathdelimitedstack = s.mathdelimitedstack, } checkmath(di) i = i + 1 elseif s.mathfractionstack then di.attributes = { mathfractionstack = s.mathfractionstack, } checkmath(di) i = i + 1 else if n > 0 then local d1 = d[1] local tg = d1.tg if tg == "mfrac" then if n == 1 then -- simple one di = d1 data[i] = di elseif n == 2 and d[2].tg == "ignore" then -- simple one with nilled middle fence -- already wiped anyway di = d1 data[i] = di end -- elseif tg == "mo" then -- if i > 1 and d1.data[1].content == separator then -- markapplicationof(di,data,i) -- end end end checkmath(di) i = i + 1 end elseif tg == "formulacaption" then -- formulanumber di.tg = "ignore" i = i + 1 elseif tg == "subformula" then di.tg = "mrow" checkmath(di) i = i + 1 else -- or first check for tg local s = specifications[di.fulltag] if not s then -- whatever elseif tg == "mfrac" then if s.mathfractionrule == "no" then di.attributes = { rulethickness = "0" } end elseif tg == "msup" then if s.limits then di.element = "mover" end elseif tg == "msub" then if s.limits then di.element = "munder" end elseif tg == "msubsup" then if s.limits then di.element = "munderover" end end -- checkmath(di) i = i + 1 end else -- can be string or boolean if parenttg ~= "mtext" and di == " " then data[i] = false end i = i + 1 end end end end local function stripmath(di) if not di then -- elseif di.content then return di else local tg = di.tg if tg == "mtext" or tg == "ms" then return di elseif tg == "mspace" then return di else local data = di.data local ndata = #data local n = 0 for i=1,ndata do local d = data[i] if d and not d.content then d = stripmath(d) end if d then local content = d.content -- if d.tg == "mspace" then -- n = n + 1 -- data[n] = d -- d.data = { } -- elseif not content then -- n = n + 1 -- data[n] = d -- d.__i__ = n -- hm -- else -- n = n + 1 -- data[n] = d -- end n = n + 1 data[n] = d if d.tg == "mspace" then d.data = { } elseif not content then d.__i__ = n -- hm end end end for i=ndata,n+1,-1 do data[i] = nil end -- maybe integrate the above and this one: collapse_all_mn(data) -- if #data > 0 then return di end end end end function checks.math(di) -- inspect(di) if di.skip == "comment" then -- already done, kind of weird, happens in mathmatrix, maybe some collapse -- issue that i need to look into else local specification = specifications[di.fulltag] local mode = specification.mode == "display" and "block" or "inline" local domain = specification.domain or "default" local family = specification.family or "regular" local style = specification.style or 2 -- text di.attributes = { -- ["xmlns:m"] = mathmlns, ["xmlns"] = mathmlns, ["display"] = mode, ["alttext"] = specification.input, -- bonus, will go -- ["class"] = style < 2 and ".math_display_style" or ".math_text_style", ["displaystyle"] = style < 2 and "true" or "false", ["data-lmtx-blob"] = specification.blob, ["data-lmtx-domain"] = domain ~= "default" and domain or nil, ["data-lmtx-family"] = family ~= "regular" and family or nil, } -- can be option if needed: if specification.standalone then di.nature = "display" elseif mode == "inline" then -- di.nature = "mixed" -- else spacing problem (maybe inline) di.nature = "inline" -- we need to catch x$X$x and x $X$ x else di.nature = "display" end if automathstrip then stripmath(di) end checkmath(di) end end -- this one can replace some of the previous code .. todo (test on mathmatrix) local mprescripts = { element = "mprescripts", data = { content = "" }, nature = "inline", -- mixed fulltag = "mprescripts>0" } local function wrapup(d,index,n,prelist,kernel,postlist) local di = d[index] local post = #postlist local pre = #prelist local list = { kernel } local size = 1 for i=index+1,n do d[i].skip = "ignore" end if post > 0 then for i=1,post do size = size + 1 ; list[size] = postlist[i] end end if pre > 0 then size = size + 1 ; list[size] = mprescripts for i=1,pre do size = size + 1 ; list[size] = prelist[i] end end di.element = "mmultiscripts" -- di.tg = "mmultiscripts" di.data = list di.iscontinuation = true di.__i__ = size end local function found(d,what) if what then for i=1,#d do local di = d[i] local sp = specifications[di.fulltag] if sp and sp.script == what then return di end end else for i=1,#d do local di = d[i] local sp = specifications[di.fulltag] if not sp or not sp.script then return di end end end end -- todo: pick up kernel function checks.mrow(di) local d = di.data if d then local postlist = nil local prelist = nil local kernel = nil local index = 0 for i=1,#d do local di = d[i] local data = di.data local tg = di and di.tg local sp = specifications[di.fulltag] local continuation = sp and sp.continuation if continuation then -- head kernel next if postlist and continuation.head then wrapup(d,index,#d,prelist,kernel,postlist) postlist = nil prelist = nil kernel = 0 end -- play safe as we can have empty ones (so maybe move the extra code here) local nd = #data if tg == "msup" then if not postlist then index = i postlist = { } prelist = { } kernel = dummy_nucleus end local sup = found(data,"sup") postlist[#postlist+1] = dummy_nucleus postlist[#postlist+1] = sup if continuation.kernel then kernel = data[1] or dummy_nucleus end elseif tg == "msub" then if not postlist then index = i postlist = { } prelist = { } kernel = dummy_nucleus end local sub = found(data,"sub") postlist[#postlist+1] = sub postlist[#postlist+1] = dummy_nucleus if continuation.kernel then kernel = data[1] or dummy_nucleus end elseif tg == "msubsup" then if not postlist then index = i postlist = { } prelist = { } kernel = dummy_nucleus end local sub = found(data,"sub") local sup = found(data,"sup") postlist[#postlist+1] = sub or dummy_nucleus postlist[#postlist+1] = sup or dummy_nucleus if continuation.kernel then kernel = data[1] or dummy_nucleus end elseif tg == "mmultiscripts" then -- we know that we only have one set if not postlist then index = i postlist = { } prelist = { } kernel = dummy_nucleus end local prime = found(data,"prime") local sub = found(data,"sub") local sup = found(data,"sup") local presub = found(data,"presub") local presup = found(data,"presup") if prime then postlist[#postlist+1] = dummy_nucleus postlist[#postlist+1] = prime end if sub or sup then postlist[#postlist+1] = sub or dummy_nucleus postlist[#postlist+1] = sup or dummy_nucleus end if presub or presup then prelist[#prelist+1] = presub or dummy_nucleus prelist[#prelist+1] = presup or dummy_nucleus end if continuation.kernel then kernel = data[1] or dummy_nucleus end else postlist = nil prelist = nil kernel = nil end elseif postlist then wrapup(d,index,i-1,prelist,kernel,postlist) postlist = nil prelist = nil kernel = nil end end if postlist then wrapup(d,index,#d,prelist,kernel,postlist) end end end -- we can move more checks here local function flatten(di) local r = di.__p__ while r do local d = r.data local n = #d if d and n > 1 then n = checked(d,n) end local tg = r.tg if n == 1 and (tg == "mtext" or tg == "mrow") then r.skip = "comment" -- weird error r = r.__p__ else break end end end function checks.mtable(di) flatten(di) local d = di.data -- -- left / right -- -- local main = { } -- -- local left = { } -- local max = 0 -- -- -- for i=1,#d do -- local d = d[i] -- if d.tg == "mtr" then -- if #d.data > max then -- max = #d.data -- end -- end -- end -- for i=1,#d do -- local d = d[i] -- if d.tg == "mtr" then -- if #d.data < max then -- local first = d.data[1] -- specifications[first.fulltag].cols = max -- first.attributes = { columnspan = max } -- end -- end -- end -- if next(left) then -- max = max + 1 -- for i=1,#main do -- insert(main[i].data,1,left[i] or { -- tg = "mtd", -- element = "mtd", -- nature = "mixed", -- data = { }, -- }) -- end -- end -- for i=1,#main do -- print(#main[i].data,max) -- if #main[i].data < max then -- insert(main[i].data,{ -- tg = "mtd", -- element = "mtd", -- nature = "mixed", -- data = { }, -- }) -- end -- end -- -- if next(right) then -- -- for i=1,#main do -- -- insert(main[i].data,right[i] or { -- -- tg = "mtd", -- -- element = "mtd", -- -- nature = "mixed", -- -- data = { }, -- -- }) -- -- end -- -- end -- di.data = main for i=1,#d do local d = d[i] if d.tg == "mtr" then local d = d.data for i=1,#d do local d = d[i] if d.tg == "mtd" then -- okay elseif d.content then d.content = "" else d.skip = "comment" -- weird error end end elseif d.content then d.content = "" else d.skip = "comment" -- weird error end end end function extras.mmultiscripts(di,element,n,fulltag) if not di.iscontinuation then local d = di.data local sup = found(d,"sup") local sub = found(d,"sub") local presup = found(d,"presup") local presub = found(d,"presub") local prime = found(d,"prime") local n = 1 if sup or sub then n = n + 1 ; d[n] = sub or dummy_nucleus n = n + 1 ; d[n] = sup or dummy_nucleus end if prime then n = n + 1 ; d[n] = dummy_nucleus n = n + 1 ; d[n] = prime end if presup or presub then n = n + 1 ; d[n] = mprescripts n = n + 1 ; d[n] = presub or dummy_nucleus n = n + 1 ; d[n] = presup or dummy_nucleus end d.__i__ = n end end function extras.msub(di,element,n,fulltag) local data = di.data local nuc = found(data) local sub = found(data,"sub") data[1] = nuc or dummy_nucleus data[2] = sub or dummy_nucleus data.__i__ = 2 local sp = specifications[di.fulltag] if attributes then attributes.mathsubindex = sp.subindexed else di.attributes = { mathsubindex = sp.subindexed, } end end function extras.msup(di,element,n,fulltag) local data = di.data local nuc = found(data) local sup = found(data,"sup") or found(data,"prime") data[1] = nuc or dummy_nucleus data[2] = sup or dummy_nucleus data.__i__ = 2 local sp = specifications[di.fulltag] local attributes = di.attributes if attributes then attributes.mathsupindex = sp.supindexed else di.attributes = { mathsupindex = sp.supindexed, } end end function extras.msubsup(di,element,n,fulltag) local data = di.data local nuc = found(data) local sup = found(data,"sup") or found(data,"prime") local sub = found(data,"sub") data[1] = nuc or dummy_nucleus data[2] = sub or dummy_nucleus data[3] = sup or dummy_nucleus data.__i__ = 3 local sp = specifications[di.fulltag] if attributes then attributes.mathsupindex = sp.supindexed attributes.mathsubindex = sp.subindexed else di.attributes = { mathsupindex = sp.supindexed, mathsubindex = sp.subindexed, } end end function extras.munder(di,element,n,fulltag) if di.tg == "msub" then extras.msub(di,element,n,fulltag) end end function extras.mover(di,element,n,fulltag) if di.tg == "msup" then extras.msup(di,element,n,fulltag) end end function extras.munderover(di,element,n,fulltag) if di.tg == "msubsup" then extras.msubsup(di,element,n,fulltag) end end end