if not modules then modules = { } end modules ['math-tag'] = {
    version   = 1.001,
    comment   = "companion to math-ini.mkiv",
    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
    copyright = "PRAGMA ADE / ConTeXt Development Team",
    license   = "see context related readme files"
}

-- todo: have a local list with local tags that then get appended
-- todo: use tex.getmathcodes (no table)
-- todo: add more spacing details + check text stuff for latest additions
-- todo: some more font related cleanup + adaption to new scaling
-- todo: tracing
-- todo: maybe use lpeg matchers

-- todo: prime
-- todo: middle in fraction

local find, match, gsub = string.find, string.match, string.gsub
local insert, remove, concat, setmetatableindex = table.insert, table.remove, table.concat, table.setmetatableindex

local attributes         = attributes
local nodes              = nodes

local nuts               = nodes.nuts
local tonut              = nuts.tonut

local getchar            = nuts.getchar
local getnext            = nuts.getnext
local getprev            = nuts.getprev
local getdata            = nuts.getdata
local getlist            = nuts.getlist
local getfield           = nuts.getfield
local getdisc            = nuts.getdisc
local getattr            = nuts.getattr
local setattr            = nuts.setattr
local getwidth           = nuts.getwidth
local getoptions         = nuts.getoptions
local getclass           = nuts.getclass
local getprop            = nuts.getprop
local getcharspec        = nuts.getcharspec
local getchardict        = nuts.getchardict

local getnumerator       = nuts.getnumerator
local getdenominator     = nuts.getdenominator
local getdelimiter       = nuts.getdelimiter
local getleftdelimiter   = nuts.getleftdelimiter
local getrightdelimiter  = nuts.getrightdelimiter
local gettopdelimiter    = nuts.gettopdelimiter
local getbottomdelimiter = nuts.getbottomdelimiter
local getdegree          = nuts.getdegree
local gettop             = nuts.gettop
local getbottom          = nuts.getbottom
local getchoice          = nuts.getchoice
local getnucleus         = nuts.getnucleus

local setattributes      = nuts.setattributes
local nextnode           = nuts.traversers.node

local nodecodes              = nodes.nodecodes

local accentcodes            <const> = nodes.accentcodes
local fencecodes             <const> = nodes.fencecodes
local fractioncodes          <const> = nodes.fractioncodes
local kerncodes              <const> = nodes.kerncodes

local noad_code              <const> = nodecodes.noad
local accent_code            <const> = nodecodes.accent
local radical_code           <const> = nodecodes.radical
local fraction_code          <const> = nodecodes.fraction
local subbox_code            <const> = nodecodes.subbox
local submlist_code          <const> = nodecodes.submlist
local mathchar_code          <const> = nodecodes.mathchar
local mathtextchar_code      <const> = nodecodes.mathtextchar
local delimiter_code         <const> = nodecodes.delimiter
local style_code             <const> = nodecodes.style
local choice_code            <const> = nodecodes.choice
local fence_code             <const> = nodecodes.fence

local hlist_code             <const> = nodecodes.hlist
local vlist_code             <const> = nodecodes.vlist
local glyph_code             <const> = nodecodes.glyph
local disc_code              <const> = nodecodes.disc
local glue_code              <const> = nodecodes.glue
local kern_code              <const> = nodecodes.kern
local rule_code              <const> = nodecodes.rule
local math_code              <const> = nodecodes.math

local fixedtopaccent_code    <const> = accentcodes.fixedtop
local fixedbottomaccent_code <const> = accentcodes.fixedbottom
local fixedbothaccent_code   <const> = accentcodes.fixedboth

local leftfence_code         <const> = fencecodes.left
local middlefence_code       <const> = fencecodes.middle
local rightfence_code        <const> = fencecodes.right
local operatorfence_code     <const> = fencecodes.operator

local atop_code              <const> = fractioncodes.atop
local above_code             <const> = fractioncodes.above

local fontkern_code          <const> = kerncodes.fontkern
local italiccorrection_code  <const> = kerncodes.italiccorrection

local hextensible_code       <const> = nodes.radicalcodes.hextensible
local delimited_code         <const> = nodes.radicalcodes.delimited
----- delimiterover_code     <const> = nodes.radicalcodes.delimiterover
----- delimiterunder_code    <const> = nodes.radicalcodes.delimiterunder
----- overdelimiter_code     <const> = nodes.radicalcodes.overdelimiter
----- underdelimiter_code    <const> = nodes.radicalcodes.underdelimiter

local lextensible_code       <const> = nodes.listcodes.hextensible
local gextensible_code       <const> = nodes.glyphcodes.extensible

local a_tagged               <const> = attributes.private('tagged')
local a_mathcategory         <const> = attributes.private('mathcategory')
local a_mathstack            <const> = attributes.private('mathstack')
----- a_mathmode             <const> = attributes.private('mathmode')
local a_mathfamily           <const> = attributes.private('mathfamily')
local a_mathdomain           <const> = attributes.private('mathdomain')

local c_mathblobnesting      <const> = tex.iscount("currentmathblobnesting")
local c_mathblob             <const> = tex.iscount("currentmathblob")

local processnoads    = noads.process

local getintegervalue = tex.getintegervalue
local getmacro        = tokens.getters.macro

local tags            = structures.tags

local start_tagged    = tags.start
local restart_tagged  = tags.restart
local push_tagged     = tags.push
local pop_tagged      = tags.pop
local stop_tagged     = tags.stop
local taglist         = tags.taglist

----- chardata        = characters.data

local getmathcodes    = tex.getmathcodes
local classes         = mathematics.classes

local classtotag = {
    [classes.ordinary]     = "mi",
    [classes.variable]     = "mi",
    [classes.imaginary]    = "mi",
    [classes.differential] = "mi",
    [classes.exponential]  = "mi",
    [classes.digit]        = "mn",
    [classes.implication]  = "mo",
    [classes.ghost]        = "mo",--always?
--  [classes.relation]     = "mo",
--  [classes.binary]       = "mo",
--  [classes.punctuation]  = "mc",
}

local fromunicode16  = fonts.mappings.fromunicode16
local fontcharacters = fonts.hashes.characters

local report_tags = logs.reporter("structure","tags")

local tagging = {
 -- mfenced = true,
    mfenced = false,
}

directives.register("structures.tags.math.mfenced", function(v) tagging.mfenced = v end)

mathematics.tagging = tagging

local process, processsubsup  do

    local noadoptioncodes = tex.noadoptioncodes

    local continuation_code          <const> = noadoptioncodes.continuation
    local continuationhead_code      <const> = noadoptioncodes.continuationhead
    local continuationkernel_code    <const> = noadoptioncodes.continuationkernel

    local indexedsuperscript_code    <const> = noadoptioncodes.indexedsuperscript
    local indexedsubscript_code      <const> = noadoptioncodes.indexedsubscript
    local indexedsuperprescript_code <const> = noadoptioncodes.indexedsuperprescript
    local indexedsubprescript_code   <const> = noadoptioncodes.indexedsubprescript

    local limits_code                <const> = noadoptioncodes.limits

 -- x[prime + sup]       : <msup>    <msup>    <mi>x</mi> <mi>sup</mi> </msup>              <mi>prime</mi> </msup>
 -- x[prime + sub]       : <msubsup>           <mi>x</mi> <mi>sub</mi> </msubsup>           <mi>prime</mi> </msubsup>
 -- x[prime + sup + sub] : <msup>    <msubsup> <mi>x</mi> <mi>sub</mi> <mi>sup</mi> </msup> <mi>prime</mi> </msup>
 -- x[prime + sup + sub] : <msubsup>           <mi>x</mi> <mi>sub</mi> </msubsup>           <mi>prime</mi> </msubsup>
 --
 -- x[prime]             : <msup>              <mi>x</mi> <mi>prime</mi>            </msup>
 -- x[sup]               : <msup>              <mi>x</mi> <mi>sup</mi>              </msup>
 -- x[sub]               : <msub>              <mi>x</mi> <mi>sub</mi>              </msub>
 -- x[sup + sub]         : <msubsup>           <mi>x</mi> <mi>sub</mi> <mi>sup</mi> </msubsup>

    local t_prime = { script = "prime" } -- etc, todo: set on main table

    local function simple(start,nucleus,prime,sup,sub,options,continuation,limits)
        if prime then
            setattr(start,a_tagged,start_tagged("msup", { prime = true }))
            start_tagged("mrow")
        end
        if sub then
            if sup then
                local subindexed = ((options & indexedsubscript_code  ) > 0) or nil
                local supindexed = ((options & indexedsuperscript_code) > 0) or nil
                if continuation then
                    continuation.subindexed = subindexed
                    continuation.supindexed = supindexed
                    continuation.limits     = limits
                else
                    continuation = {
                        subindexed = subindexed,
                        supindexed = supindexed,
                        limits     = limits,
                    }
                end
                setattr(start,a_tagged,start_tagged("msubsup",continuation))
                process(nucleus)
                start_tagged("mrow", { script = "sub" })
                process(sub)
                stop_tagged()
                start_tagged("mrow", { script = "sup" })
                process(sup)
                stop_tagged()
                stop_tagged()
            else
                local subindexed = ((options & indexedsubscript_code) > 0) or nil
                if continuation then
                    continuation.subindexed = subindexed
                    continuation.limits     = limits
                else
                    continuation = {
                        subindexed = subindexed,
                        limits     = limits,
                    }
                end
                setattr(start,a_tagged,start_tagged("msub",continuation))
                process(nucleus)
                start_tagged("mrow", { script = "sub" })
                process(sub)
                stop_tagged()
                stop_tagged()
            end
        elseif sup then
            local supindexed = ((options & indexedsuperscript_code) > 0) or nil
            if continuation then
                continuation.supindexed = supindexed
                continuation.limits     = limits
            else
                continuation = {
                    supindexed = supindexed,
                    limits     = limits,
                }
            end
            setattr(start,a_tagged,start_tagged("msup",continuation))
            process(nucleus)
            start_tagged("mrow", { script = "sup" })
            process(sup)
            stop_tagged()
            stop_tagged()
        else
            process(nucleus)
        end
        if prime then
            stop_tagged()
            start_tagged("mrow", { script = "prime" })
            process(prime)
            stop_tagged()
            stop_tagged()
        end
    end

    local function complex(start,nucleus,prime,sup,sub,presup,presub,options,continuation,limits)
        if prime then
            setattr(start,a_tagged,start_tagged("msup", { prime = true }))
            start_tagged("mrow")
        end
-- -- makes little sense
-- if continuation then
--     continuation.limits = limits
-- else
--     continuation = { limits = limits }
-- end
        start_tagged("mmultiscripts", continuation)
        process(nucleus)
     -- if prime then
     --     start_tagged("mrow", { script = "prime" })
     --     process(prime)
     --     stop_tagged()
     -- end
        if sup then
            start_tagged("mrow", { script = "sup" }) -- , indexed = ((options & indexedsuperscript_code) > 0) and "true" or nil  })
            process(sup)
            stop_tagged()
        end
        if sub then
            start_tagged("mrow", { script = "sub" }) -- , indexed = ((options & indexedsubscript_code) > 0) and "true" or nil  })
            process(sub)
            stop_tagged()
        end
        if presup then
            start_tagged("mrow", { script = "presup" }) -- , indexed = ((options & indexedsuperprescript_code) > 0) and "true" or nil  })
            process(presup)
            stop_tagged()
        end
        if presub then
            start_tagged("mrow", { script = "presub" }) -- , indexed = ((options & indexedsubprescript_code) > 0) and "true" or nil  })
            process(presub)
            stop_tagged()
        end
        stop_tagged()
        if prime then
            stop_tagged()
            start_tagged("mrow", { script = "prime" })
            process(prime)
            stop_tagged()
            stop_tagged()
        end
    end

    local multiprime = true

    directives.register("structures.tags.math.multiprime", function(v) multiprime = v end)

    processsubsup = function(start)
        -- At some point we might need to add an attribute signaling the
        -- super- and subscripts because TeX and MathML use a different
        -- order. The mrows are needed to keep mn's separated.
        local nucleus, prime, sup, sub, presup, presub = getnucleus(start,true)
        local options = getoptions(start) or 0
        local limits = (options & limits_code) > 0
        local continuation = false
        local c = (options & continuation_code) > 0
        local h = (options & continuationhead_code) > 0
        local k = (options & continuationkernel_code) > 0
        local continuation = ((c or h or k) and {
            continuation = { next = c, head = h, kernel = k}
        }) or nil
     -- if  presup or presub  or (               sup and prime) then
        if (presup or presub) or (multiprime and sup and prime) then
            complex(start,nucleus,prime,sup,sub,presup,presub,options,continuation,limits)
        else
            simple(start,nucleus,prime,sup,sub,options,continuation,limits)
        end
    end

end

-- todo: check function here and keep attribute the same

-- todo: variants -> original

local actionstack = { }
local fencesstack = { }

-- glyph nodes and such can happen in under and over stuff

local function getunicode(n) -- instead of getchar
    local char, font = getcharspec(n)
    local data = fontcharacters[font][char]
    return data.unicode or char -- can be a table but unlikely for math characters
end

-------------------

local content = { }
local found   = false

content[mathchar_code] = function() found = true end

local function hascontent(head)
    found = false
    processnoads(head,content,"content")
    return found
end

--------------------

-- todo: use properties

-- local function showtag(n,id,old)
--     local attr = getattr(n,a_tagged)
--     local curr = tags.current()
--     report_tags("%s, node %s, attr %s:%s (%s), top %s (%s)",
--         old and "before" or "after ",
--         nodecodes[id],
--         getattrlist(n),
--         attr or "?",attr and taglist[attr].tagname or "?",
--         curr or "?",curr and taglist[curr].tagname or "?"
--     )
-- end

-- I need to bring this in sync with new or removed mathml 3, not that there has
-- been many changes. It will happen in sync with other mathml updates in context
-- where we also keep adapting to a cycling between either or not support in
-- browsers, the come-and-go of alternatives like ascii math and mathjax. It's the
-- web and browser support that drives this, not tex and its community. So, maybe
-- I'll add some more detail here, nto that it matters much in the long run where we
-- only focus on structure and let the engine deal with the details. Another reason
-- to update this is that we can add some tracing (lmtx only).

-- This has been working ok for quite but in 2023 it's time to have a look at it
-- again and see to what extend we need to adapt to new features. Around the time
-- PG's Panopticom was put on youtube.

local chardata = characters.data

local mathnames   = mathematics.dictionaries.names
local mathgroups  = mathematics.dictionaries.groups
local mathclasses = mathematics.classes

local everygroup      <const> = mathematics.dictionaries.names.everygroup
local variable_class  <const> = classes.variable

local function getproperties(n,class)
    local props, group, index, font, char = getchardict(n)
    if group == everygroup then
        -- should be done at the lua end by defining a char bound group code
        local c = chardata[char]
        if c then
            local g = c.mathgroup
            if not g then
                local s = c.mathspec
                if s then
                    g = s[1].group
                end
                if g then
                    group = mathnames[g] or group
                end
            end
        end
    end
    if group or class then
        local swapped = getprop(n,"swappedclass")
        if swapped then
            class = swapped
        end
        return {
            mathclass     = mathclasses[class] or class,
            mathgroup     = mathgroups[group],
            mathindex     = index,
         -- mathfont      = font,
            mathcharacter = char,
        }
    end
end

-- todo: why seen twice
-- todo: get rid of detail

do

    local level = -1
    local trace = false

    trackers.register("export.trace.math", function(v) trace = v end)

    local function show(n,id,where)
-- inspect(getattr(n,a_tagged))
-- inspect(taglist)
        local a = getattr(n,a_tagged)
        local s = a and taglist[a]
        if s then
            s = s.taglist
        end
        report_tags("%w %s : %S : %s : %s",
            level,
            where,
            nodecodes[id],
            a or "?",
            s and concat(s," ", 3) or "untagged"
        )
    end

    -- Beware, the first node in list is the actual list so we definitely need to nest.
    -- This approach is a hack, maybe I'll make a proper nesting feature to deal with
    -- this at another level. Here we just fake structure by enforcing the inner one.

    -- todo: have a local list with local tags that then get appended

    local mathcodes                    = nodes.mathcodes
    local beginmath_code       <const> = mathcodes.beginmath
    local endmath_code         <const> = mathcodes.endmath
    local beginbrokenmath_code <const> = mathcodes.beginbrokenmath
    local endbrokenmath_code   <const> = mathcodes.endbrokenmath

    local function runner(td,nd,text,list) -- quite inefficient
        local cache = { } -- we can have nested unboxed mess so best local to runner
        local keep = nil
     -- local keep = { } -- in case we might need to move keep outside
        for n, id, subtype in nextnode, list do
            local mth = id == math_code and subtype
            if mth == beginmath_code or mth == beginbrokenmath_code then
             -- begin in line / nested math box like stackers
             -- insert(keep,text)
                keep = text
                text = start_tagged("mrow")
             -- common = common + 1
            end
            local aa = getattr(n,a_tagged)
            if aa then
                -- here we could intercept formulacaption and formulanumber
                local ac = cache[aa]
                if not ac then
                    local tagspec = taglist[aa]
                    local tagdata = tagspec.taglist
                    local common  = 0
                    for i=1,nd do
                        if td[i] == tagdata[i] then
                            common = common + 1
                        else
                            break
                        end
                    end
                    common = common + 1
                    local extra = #tagdata
                    if common <= extra then
                        if trace then
                            show(n,id," ")
                        end
                        for i=common,extra do
                            -- don't we loose properties here?
                            ac = restart_tagged(tagdata[i])
                        end
                        for i=common,extra do
                            stop_tagged()
                        end
                    else
                        ac = text
                    end
                    cache[aa] = ac
                end
                setattr(n,a_tagged,ac)
            else
                setattr(n,a_tagged,text)
            end
            if id == hlist_code or id == vlist_code then
                runner(td,nd,text,getlist(n))
         -- elseif id == glyph_code then
            elseif id == disc_code then
                -- this should not be needed
                local pre, post, replace = getdisc(n)
                if pre then
                    runner(td,nd,text,pre)
                end
                if post then
                    runner(td,nd,text,post)
                end
                if replace then
                    runner(td,nd,text,replace)
                end
            end
            if mth == endmath_code or mth == endbrokenmath_code then
                -- end in line
                stop_tagged()
             -- text = remove(keep)
                text = keep
             -- common = common - 1
            end
        end
    end

    local function relocate(start,attr,tag,specification)
        -- we can now end up with nexted "math"
        local detail = specification.detail
        local text   = start_tagged(tag, detail and { detail = detail } or nil)
        setattr(start,a_tagged,text)
        local list = getlist(start)
        if list then
            local tagdata = specification.taglist
            runner(tagdata,#tagdata,text,list)
        end
        stop_tagged()
    end

    local function makeintotext(start)
        setattr(start,a_tagged,start_tagged("mtext"))
        stop_tagged()
    end

    process = function(start) -- we cannot use the processor as we have no finalizers (yet)
        local mtexttag = nil
        level = level + 1
        for start, id, subtype in nextnode, start do -- current
            if trace then
                show(start,id,"+")
            end
            if id == glyph_code or id == disc_code then
                if not mtexttag then
                    mtexttag = start_tagged("mtext")
                end
                setattr(start,a_tagged,mtexttag)
            elseif mtexttag and id == kern_code and (subtype == fontkern_code or subtype == italiccorrection_code) then
                setattr(start,a_tagged,mtexttag)
            elseif id == rule_code then
             -- setattr(start,a_tagged,start_tagged("ignore"))
             -- stop_tagged()
            else
                if mtexttag then
                    stop_tagged()
                    mtexttag = nil
                end
                if id == mathchar_code then
                 -- local char = getchar(start) -- not used
                    local properties = getproperties(start,subtype)
                    local tag = classtotag[subtype] or "mo"
                    properties.mathstack = getattr(start,a_mathstack)
                    properties.mathcategory = getattr(start,a_mathcategory)
                    setattr(start,a_tagged,start_tagged(tag,properties))
                    stop_tagged()
                    if trace then
                        show(start,id,"C")
                    end
                    level = level - 1
                    break -- okay?
                elseif id == mathtextchar_code then -- or id == glyph_code
                    -- check for code
                    local a = getattr(start,a_mathcategory)
                    if a then -- cache table
                        setattr(start,a_tagged,start_tagged("ms",{ mathcategory = a })) -- mtext
                    else
                        setattr(start,a_tagged,start_tagged("ms")) -- mtext
                    end
                    stop_tagged()
                    if trace then
                        show(start,id,"T")
                    end
                    level = level - 1
                    break
                elseif id == delimiter_code then
                    -- check for code
                    local properties = getproperties(start,subtype)
                    properties.delimiter = "true" -- inspect(properties)
                    setattr(start,a_tagged,start_tagged("mo",properties))
                    stop_tagged()
                    if trace then
                        show(start,id,"D")
                    end
                    level = level - 1
                   break
                elseif id == style_code then
                    -- has a next
                elseif id == noad_code then
                    processsubsup(start)
                elseif id == subbox_code or id == hlist_code or id == vlist_code then
                    -- keep an eye on subbox_code and see what ends up in there
                    -- a hlist can be a nested result (mlist_to_hlist)
                    local attr = getattr(start,a_tagged)
                    if attr then
                        local specification = taglist[attr]
                        if specification then
                            local tag = specification.tagname
--                             if tag == "formulacaption" then
--                                 -- can this happen, skip
--                             else
                            if tag == "mstacker" then
                                -- this still happens
                                local list = getlist(start)
                                if list then
                                    process(list)
                                end
                            else
                                if tag ~= "math" and
                                   tag ~= "mtable" and tag ~= "mtd" and
                                   tag ~= "mstackertop" and tag ~= "mstackermid" and tag ~= "mstackerbot"
-- and tag ~= "formulacaption"
-- and tag ~= "formulanumber"

                                then
                                   tag = "mtext"
                                end
                                relocate(start,attr,tag,specification)
                            end
                        else
                            makeintotext(start) -- last resort gamble
                        end
                    else
                        makeintotext(start) -- last resort gamble
                    end
                elseif id == submlist_code then -- normally a hbox
                    local list = getlist(start)
                    if list then
                        local attr = getattr(start,a_tagged)
                        local last = attr and taglist[attr]
                        if last then
                            local tag    = last.tagname
                            local detail = last.detail
                            if tag == "munit" then
                                setattr(start,a_tagged,start_tagged("mrow", {
                                    mathunit = detail
                                }))
                                process(list)
                                stop_tagged()
                            elseif tag == "mfunction" then
                                setattr(start,a_tagged,start_tagged("mrow", {
                                    mathfunction = detail,
                                    mathcategory = getattr(start,a_mathcategory),
                                    mathstack    = getattr(start,a_mathstack),
                                }))
                                process(list)
                                stop_tagged()
                            elseif tag == "mstacker" then
                                setattr(start,a_tagged,restart_tagged(attr)) -- so we just reuse the attribute
                                process(list)
                                stop_tagged()
                            elseif tag == "mfunctionstack" then
                                setattr(start,a_tagged,start_tagged("mrow", {
                                    mathfunctionstack = detail,
                                    mathstack         = getattr(start,a_mathstack),
                                }))
                                process(list)
                                stop_tagged()
                            elseif tag == "mfractionstack" then
                                setattr(start,a_tagged,start_tagged("mrow", {
                                    mathfractionstack = detail,
                                 -- mathstack         = getattr(start,a_mathstack),
                                }))
                                process(list)
                                stop_tagged()
                            elseif tag == "mdelimitedstack" then
                                setattr(start,a_tagged,start_tagged("mrow", {
                                    mathdelimitedstack = detail,
                                 -- mathstack          = getattr(start,a_mathstack),
                                }))
                                process(list)
                                stop_tagged()
                            elseif tag == "mdigits" then
                                setattr(start,a_tagged,start_tagged("mrow", {
                                    mathdigits = detail or "unknown",
                                }))
                                process(list)
                                stop_tagged()
                            else
                                setattr(start,a_tagged,start_tagged("mrow"))
                                process(list)
                                stop_tagged()
                            end
                        else -- never happens, we're always document
                            setattr(start,a_tagged,start_tagged("mrow"))
                            process(list)
                            stop_tagged()
                        end
                    end
                elseif id == fraction_code then
                    --
                    -- if middle then we have a stacker!
                    --
                    local num    = getnumerator(start)
                    local denom  = getdenominator(start)
                    local left   = getleftdelimiter(start)
                    local right  = getrightdelimiter(start)
                    local middle = getdelimiter(start) -- todo get them all in one go
                    if left then
                       setattr(left,a_tagged,start_tagged("mo"))
                       process(left)
                       stop_tagged()
                    end
                    setattr(start,a_tagged,start_tagged("mfrac", {
                        mathfractionrule = (subtype == atop_code or subtype == above_code) and "no" or "yes",
                    }))
                    process(num)
                    process(denom)
                    stop_tagged()
                    if middle then
                        setattr(middle,a_tagged,start_tagged("ignore"))
                        stop_tagged()
                    end
                    if right then
                        setattr(right,a_tagged,start_tagged("mo"))
                        process(right)
                        stop_tagged()
                    end
                elseif id == choice_code then
                    local display      = getchoice(start,1)
                    local text         = getchoice(start,2)
                    local script       = getchoice(start,3)
                    local scriptscript = getchoice(start,4)
                    if display then
                        process(display)
                    end
                    if text then
                        process(text)
                    end
                    if script then
                        process(script)
                    end
                    if scriptscript then
                        process(scriptscript)
                    end
                elseif id == fence_code then
                    local delimiter    = getdelimiter(start)
                    local mfenced      = tagging.mfenced
                    local mathcategory = getattr(start,a_mathcategory)
                    if subtype == leftfence_code then
                        local properties = { mathcategory = mathcategory }
                        insert(fencesstack,properties)
                        setattr(start,a_tagged,start_tagged("mfenced",properties)) -- needs checking
                        if delimiter then
                            if mfenced then
                                start_tagged("ignore")
                            end
                            local chr = getchar(delimiter)
                            if chr ~= 0 then
                                properties.left = chr
                            end
                            process(delimiter)
                            if mfenced then
                                stop_tagged()
                            end
                        end
                        start_tagged("mrow") -- begin of subsequence
                    elseif subtype == middlefence_code then
                        stop_tagged()        -- end of subsequence
                        if delimiter then
                            if mfenced then
                                start_tagged("ignore")
                            end
                            local top = fencesstack[#fencesstack]
                            local chr = getchar(delimiter)
                            if chr ~= 0 then
                                local mid = top.middle
                                if mid then
                                    mid[#mid+1] = chr
                                else
                                    top.middle = { chr }
                                end
                            end
                            process(delimiter)
                            if mfenced then
                                stop_tagged()
                            end
                        end
                        start_tagged("mrow") -- begin of subsequence
                    elseif subtype == rightfence_code then
                        stop_tagged() -- end of subsequence
                        local properties = remove(fencesstack)
                        if not properties then
                            report_tags("missing right fence")
                            properties = { }
                        end
                        if delimiter then
                            if mfenced then
                                start_tagged("ignore")
                            end
                            local chr = getchar(delimiter)
                            if chr ~= 0 then
                                properties.right = chr
                            end
                            process(delimiter)
                            if mfenced then
                                stop_tagged()
                            end
                        end
                        stop_tagged()
                    elseif subtype == operatorfence_code then
                        local properties = {
                            operator     = true,
                            mathcategory = mathcategory,
                        }
                        local top = gettopdelimiter(start)
                        local bot = getbottomdelimiter(start)
                        insert(fencesstack,properties)
                        setattr(start,a_tagged,start_tagged("mrow",properties))
                        if top then
                            if bot then
                                start_tagged("msubsup")
                            else
                                start_tagged("msup")
                            end
                        elseif bot then
                            start_tagged("msub")
                        else
                            start_tagged("mrow",properties)
                        end
                        if delimiter then
                            start_tagged("mrow")
                            local chr = getchar(delimiter)
                            if chr ~= 0 then
                                properties.left = chr
                            end
                            process(delimiter)
                            stop_tagged()
                        else
                            -- error
                        end
                        if top or bot then
                            if top then
                                start_tagged("mrow", { script = "sup" })
                                process(top)
                                stop_tagged()
                            end
                            if bot then
                                start_tagged("mrow", { script = "sub" })
                                process(bot)
                                stop_tagged()
                            end
                        end
                        stop_tagged()
                        start_tagged("mrow") -- begin of subsequence
                    else -- no fence
                        local a = getattr(start,a_mathcategory)
                        local properties = a and { mathcategory = a }
                        start_tagged("mrow",properties) -- begin of subsequence
                        if delimiter then
                            if mfenced then
                                start_tagged("ignore")
                            end
                            process(delimiter)
                            if mfenced then
                                stop_tagged()
                            end
                        end
                        stop_tagged()
                    end
                elseif id == radical_code then
                    local left  = getleftdelimiter(start)
                    local right = getrightdelimiter(start)
                    if subtype == hextensible_code then
                        -- eventually we have no radical but just some box
                        if left then
                            start_tagged("mo")
                            process(left)
                            stop_tagged()
                        end
                    elseif subtype == delimited_code then
                        start_tagged("mdelimited",properties)
                        if left then
                            local properties = getproperties(left,subtype) or { }
                            properties.delimiterlocation = "left"
                            setattr(left,a_tagged,start_tagged("mo", properties))
                            stop_tagged()
                        end
                        if right then
                            local properties = getproperties(right,subtype) or { }
                            properties.delimiterlocation = "right"
                            setattr(right,a_tagged,start_tagged("mo",properties))
                            stop_tagged()
                        end
                        processsubsup(start)
                        stop_tagged()
                    else
                        local degree = getdegree(start)
                        if left then
                            start_tagged("ignore")
                            process(left) -- root symbol, ignored
                            stop_tagged()
                        end
                        if right then
                            start_tagged("ignore")
                            process(right) -- actuarian symbol, ignored
                            stop_tagged()
                        end
                        if degree and hascontent(degree) then
                            setattr(start,a_tagged,start_tagged("mroot"))
                            processsubsup(start)
                            process(degree)
                            stop_tagged()
                        else
                            setattr(start,a_tagged,start_tagged("msqrt"))
                            processsubsup(start)
                            stop_tagged()
                        end
                    end
                elseif id == accent_code then
                    -- maybe tag the 'mo' so that we can reorder but we only use
                    -- under and over anyway
                    local topaccent    = gettop(start)
                    local bottomaccent = getbottom(start)
                    local middleaccent = getdelimiter(start)
                    local mathcategory = getattr(start,a_mathcategory)
                    local topfixed     = subtype == fixedbothaccent_code or subtype == fixedtopaccent_code
                    local bottomfixed  = subtype == fixedbothaccent_code or subtype == fixedbottomaccent_code
                    if bottomaccent then
                        if topaccent then
                            setattr(start,a_tagged,start_tagged("munderover", {
                                accent       = true,
                                top          = getunicode(topaccent),
                                bottom       = getunicode(bottomaccent),
                                topfixed     = topfixed,
                                bottomfixed  = bottomfixed,
                                mathcategory = mathcategory,
                            }))
                            if topfixed    then topfixed    = nil else topfixed    = { delimiter = "true" } end
                            if bottomfixed then bottomfixed = nil else bottomfixed = { delimiter = "true" } end
                            processsubsup(start)
                            setattr(bottomaccent,a_tagged,start_tagged("mo",bottomfixed))
                            stop_tagged()
                            setattr(topaccent,a_tagged,start_tagged("mo",topfixed))
                            stop_tagged()
                            stop_tagged()
                        else
                            setattr(start,a_tagged,start_tagged("munder", {
                                accent       = true,
                                bottom       = getunicode(bottomaccent),
                                bottomfixed  = bottomfixed,
                                mathcategory = mathcategory,
                            }))
                            if bottomfixed then bottomfixed = nil else bottomfixed = { delimiter = "true" } end
                            processsubsup(start)
                            setattr(bottomaccent,a_tagged,start_tagged("mo",bottomfixed))
                            stop_tagged()
                            stop_tagged()
                        end
                    elseif topaccent then
                        setattr(start,a_tagged,start_tagged("mover", {
                            accent       = true,
                            top          = getunicode(topaccent),
                            topfixed     = topfixed,
                            mathcategory = mathcategory,
                        }))
                        if topfixed then topfixed = nil else topfixed = { delimiter = "true" } end
                        processsubsup(start)
                        setattr(topaccent,a_tagged,start_tagged("mo",topfixed))
                        stop_tagged()
                        stop_tagged()
                    else
                        processsubsup(start)
                    end
                elseif id == glue_code then
                    -- before processing, so other intermathglue is not tagged
                    local em = fonts.hashes.emwidths[nuts.getfont(start)]
                    local wd = getwidth(start)
                    if em and wd then
                        setattr(start,a_tagged,start_tagged("mspace",{ emfactor = wd/em }))
                        stop_tagged()
                    end
                else
                    -- rule boundary
                end
            end
            if trace then
                show(start,id,"-")
            end
        end
        if mtexttag then
            stop_tagged()
        end
        level = level - 1
    end

end

local standalone  = false

directives.register("structures.tags.math.standalone", function(v) standalone = v end)

local texgetattribute   = tex.getattribute
----- text_code <const> = tex.mathoptioncodes.text

function noads.handlers.tags(head,style,penalties,beginclass,endclass,level,mainstyle,options)
    --
    -- The style state is sort of tricky because we have to set that at the \TEX\ end
    -- when we start the (in our case always) inlien mode. But it looks ok.
    --
    -- The mode is a different matter, for that we have to use an attribute because we
    -- have no anchor. Alternatively we can insert some special boundary node (either
    -- a registered user boundary (single code) or an attribute one (a key and value).
    -- In the end we moved that check to a helper.
    --
    -- local mode = ((getattr(head,a_mathmode) or 0) == 1) and "display" or "inline"

    local mode = noads.getmathmode(head)

    if not context.trialtypesetting() then
        local a = texgetattribute(a_mathfamily) -- brrr
        start_tagged("math", {
            mode       = mode,
         -- text       = (options & text_code) == text_code,
            standalone = standalone,
            family     = mathematics.familyname(a),
            style      = mainstyle,
            input      = mathematics.lastinput,
            blob       = getintegervalue(c_mathblob),
            language   = getmacro("currentlanguage"),
            domain     = mathematics.getdomainname(tonumber(getattr(head,a_mathdomain))),
        })
        setattr(head,a_tagged,start_tagged("mrow"))
        if trace then
            report_tags("start math sweep at level %i, %s mode",level,mode)
        end
        process(head)
        if trace then
            report_tags("stop math sweep at level %i, %s mode",level,mode)
        end
        stop_tagged()
        stop_tagged()
    end
    mathematics.lastinput= nil
end

do

    -- This one is meant for tracing (in m4all/m4mbo where it complements some other
    -- tracing) but it actually can also replace the embedding feature although that
    -- one might be better when we have more complex code with dependencies outside
    -- the blob. I'll deal with that when it's needed (trivial). The current
    -- interface is rather minimalistic.

    local enabled  = false
    local export   = false
    local warned   = false
    local shared   = { }
    local orders   = { }
    local hashed   = { }
    local blobdone = setmetatableindex("table")

    local trace_blobs = false
    local report_blob = logs.reporter("math blob")

    directives.register("structures.tags.math.blobs", function(v) trace_blobs = v end)

    local function register(order,data)
        if not data then
            data = ""
        end
        local hash  = sha2.HEX256(data) -- maybe direct
        local index = hashed[hash]
        if index then
            if trace_blobs then
                report_blob("known, order %i, index %i, data: %s",order,index,gsub(shared[index].strip,"%s+",""))
            end
        else
            index = #shared + 1
            hashed[hash]  = index
            shared[index] = {
                data  = data,
                strip = xml.mml.stripped(data), -- we could delay this as it often only happens once
            }
            if trace_blobs then
                report_blob("register, order %i, index %i, data: %s",order,index,gsub(shared[index].strip,"%s+",""))
            end
        end
        orders[order] = index
        return index
    end

    function mathematics.enablecollecting()
        if structures.tags.enabled() then
            if not enabled then
                nodes.tasks.enableaction("math","noads.handlers.export")
            end
            enabled = true
            export  = structures.tags.localexport
        elseif not warned then
            report_tags("math collecting only works when tagging is enabled")
            warned = true
        end
    end

    function mathematics.disablecollecting()
        enabled = false
        export  = false
    end

    local function getmathblob(purpose,order)
        local index = orders[order]
        if index then
            local entry = shared[index]
            if trace_blobs then
                report_blob("get math data, purpose %a, order %i, index %i, data: %s",purpose,order,index,gsub(entry.strip,"%s+", ""))
            end
            return entry.strip
        end
    end

    local function gettextblob(purpose,language,order)
        if not order then
            order    = language
            language = "en"
        end
        local index = orders[order]
        if index then
            local entry = shared[index]
            local data  = entry and entry[language]
            if not data then
                -- when no labels we actually now duplicate but so be it
                local d = entry.data
                if d and d ~= "" then
                    data = xml.mml.verbose(d,language)
                    entry[language] = data
                else
                    entry[language] = ""
                end

            end
            if trace_blobs then
                report_blob("get text data, purpose %a, order %i, index %i, data: %s",purpose,order,index,gsub(entry.strip,"%s+", ""))
            end
            return data
        end
    end

    local function getblobindex(purpose,order)
        local index = orders[order] or 0
        if trace_blobs then
            report_blob("get math order, purpose %a, order %i, index %i",purpose,order,index)
        end
        return index
    end

    local function getblobmapping(purpose,order)
        local index   = orders[order] or 0
        local blobs   = job.variables.collected.mathblobs
        local mapping = blobs and blobs[index] or 0
        if trace_blobs then
            report_blob("get math mapping, purpose %a, order %i, index %i, mapping %04X",purpose,order,index,mapping)
        end
        return index
    end

    local function markblobindexdone(language,n)
        blobdone[language][n] = true
    end

    mathematics.getmathblob       = getmathblob
    mathematics.gettextblob       = gettextblob
    mathematics.getblobindex      = getblobindex
    mathematics.markblobindexdone = markblobindexdone

    local integer_value <const> = tokens.values.integer
    local boolean_value <const> = tokens.values.boolean

    local implement  = interfaces.implement
    local context    = context
    local ctxescaped = context.ctxescaped

    implement {
        name      = "getmathmathblob",
        protected = true,
        public    = true,
        arguments = "integer",
        actions   = function(n)
            context(getmathblob("tex",n) or "")
        end
    }

    implement {
        name      = "getmathtextblob",
        protected = true,
        public    = true,
        arguments = { "string", "integer" },
        actions   = function(language,n)
            ctxescaped(gettextblob("tex",language,n) or "")
        end
    }

    implement {
        name      = "getmathblobindex",
        public    = true,
        usage     = "value",
        arguments = "integer",
        actions   = function(n)
            return integer_value, getblobindex("tex",n)
        end
    }

    implement {
        name      = "getmathblobmapping",
        public    = true,
        usage     = "value",
        arguments = "integer",
        actions   = function(n)
            return integer_value, getblobmapping("tex",n)
        end
    }

    implement {
        name    = "lastblobindex", -- kind of obsolete
        public  = true,
        usage   = "value",
        actions = function()
            return integer_value, #shared
        end
    }

    implement {
        name      = "markblobindexdone",
        protected = true,
        public    = true,
        arguments = { "string", "integer" },
        actions   = markblobindexdone
    }

    implement {
        name      = "ifblobindexdone",
        public    = true,
        usage     = "condition",
        arguments = { "string", "integer" },
        actions = function(language,n)
            return boolean_value, blobdone[language][n]
        end
    }

    implement {
        name      = "enablecollectingmath",
     -- public    = true,
        protected = true,
        actions   = mathematics.enablecollecting
    }

    implement {
        name      = "disablecollectingmath",
     -- public    = true,
        protected = true,
        actions   = mathematics.disablecollecting
    }

    implement {
        name      = "startcollectingmath", -- obsolete
     -- public    = true,
        protected = true,
        actions   = mathematics.enablecollecting
    }

    implement {
        name      = "stopcollectingmath", -- obsolete
     -- public    = true,
        protected = true,
        actions   = function() end
    }

    -- for now here .. will become a proper lpeg

    local toascii  do

        local utfbyte = utf.byte
        local gsub, format, find = string.gsub, string.format, string.find

        local clean = setmetatableindex(function(t,k)
            local n = utfbyte(k)
            if n > 127 then
                n = format("&#x%X;",n)
            else
                n = false
            end
            t[k] = n
            return n
        end)

        local pattern = utf8.charpattern

        toascii = function(data)
            return (gsub(data,pattern,clean))
        end

    end

    implement {
        name      = "processcollectedmath",
     -- public    = true,
        protected = true,
        arguments = "4 strings",
        actions   = function(filename,buffername,n,option)
            local blob = n and tonumber(n) or getintegervalue(c_mathblob)
            local data = getmathblob("collect",blob) or ""
            if option == "ascii" then
                data = toascii(data)
            end
            if filename and filename ~= "" then
                io.savedata(filename,data)
            elseif buffername then -- always something
                buffers.assign(buffername == interfaces.variables.yes and "" or buffername,data)
            else
                return data
            end
        end
    }

    implement {
        name      = "collectedmath",
        usage     = "value",
        protected = true,
        public    = true,
        actions = function(what)
            if what == "value" then
                return integer_value, #shared
            else
                context(getmathblob("collect",tokens.scanners.integer()) or "")
            end
        end
    }

    local a_mathblob <const> = attributes.private('mathblob')

    function noads.handlers.export(head)
        if export then
-- nuts.show(head)
            local nesting = getintegervalue(c_mathblobnesting)
            local order   = getintegervalue(c_mathblob)
            if nesting == 1 then
                if trace_blobs then
                    report_blob("export blob, order %i, level %i",order,nesting)
                end
                local blob = export(head,"math")
                if blob then
                    -- something can be wrong at a page break: todo
                    blob = string.gsub(blob,"^(.-<math)","<math")
                    register(order,blob)
                else
                    if trace_blobs then
                        report_blob("empty blob, order %i, level %i",order,nesting)
                    end
                end
            else
                if trace_blobs then
                    report_blob("nested blob, order %i, level %i",order,nesting)
                end
            end
        end
        return head
    end

    nodes.tasks.appendaction("math", "finalizers", "noads.handlers.export", nil, "nonut", "disabled")

end
