if not modules then modules = { } end modules ['lxml-mms'] = {
    version   = 1.001,
    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
    comment   = "written together with Mikael Sundqvist",
    copyright = "PRAGMA ADE / ConTeXt Development Team",
    license   = "see context related readme files"
}

-- Although it sits in the lxml name space this module is pretty much hooked into
-- ConTeXt math rendering and backend code. It is one of the first applications of
-- math dictionary support. After experimenting with hooking serialization of math
-- into the rendering we decided that it made more sense to use the export
-- facilities instead. It is also a fun project that might lead so more similar
-- functionality. Of course we don't limit ourselves to English, if only because
-- our test more advanced cases are Swedish math books.
--
-- The rationale for this serialization can be found in demands for documents to be
-- accessible and math is a tricky part of that. We embed MathML in tagged PDF
-- documents and the serialization hooks into the actual text features. This is
-- needed in order to make accessibility validators (like the ones used at Lunds
-- University) happy. We let users decide how useful and reliable it is. In ConTeXt
-- you can overload and adapt bits and pieces as part of the document style. For the
-- record: we are not involved in accesibility projects, so we might get some things
-- wrong.
--
-- Timestamp: yet another entertaining drum track by Gavin Harrison "Pick Up The
-- Pieces" | Zildjian 400th UK (posted 2024/2/5) - three weeks before a PT concert
-- (YT: "The Pineapple Thief - It Leads to This).
--
-- This is experimental code that will be optimized. Also, some of it can be a bit
-- but we do that once we're done. In the end it might look simple but quite a bit
-- of exploration was involved.
--
-- We (HH & MS) need to finish some translations so feel free to test and contribute
-- to the translation tables.

local tonumber, tostring, type, next = tonumber, tostring, type, next
local concat = table.concat
local formatters = string.formatters

local xmlall       = xml.all
local xmlfirst     = xml.first
local xmltext      = xml.text
local xmlconvert   = xml.convert
local xmlcollected = xml.collected

local report = logs.reporter("mms")

local getname          = mathematics.dictionaries.name
local classes          = mathematics.classes
local getverboselabel  = mathematics.getverboselabel
local getoptionallabel = mathematics.getoptionallabel
local functions        = mathematics.categories.functions
local functiontype     = mathematics.functiontype

local function xmlevery(x)
    local dt = x.dt
    local tt
    local nn = 0
    for i=1,#dt do
        local di = dt[i]
        if di.tg and not di.special then
            if tt then
                nn = nn + 1
                tt[nn] = di
            else
                nn = 1
                tt = { di }
            end
        end
    end
    return tt, nn
end

local function xmlfixattributes(x)
    for c in xmlcollected(x,"*") do
        local at = c.at
        for k, v in next, at do
            at[k] = tostring(v)
        end
    end
end

local function xmlwipeattributes(x)
    local at = { }
    for c in xmlcollected(x,"*") do
        local at = c.at
        local colspan       = at.colspan
        local stretchy      = at.stretchy
        local scriptlevel   = at.scriptlevel
        local rulethickness = at.rulethickness
        if colspan or stretchy or scriptlevel or rulethickness then
            c.at = {
                columnspan    = colspan,       -- watch the name, different from tables
                stretchy      = stretchy,
                scriptlevel   = scriptlevel,
                linethickness = rulethickness, -- watch the name
            }
        else
            c.at = {
                -- Don't set them afterwards because they are shared!
            }
        end
    end
end

local prepare  do

    local expandtimes, expandtimesone

    expandtimesone = function(c,all,i,start,stop)
        if c then
            local tg = c.tg
            if tg == "mrow" then
                local at = c.at
                if at.mathunit then
                    -- nothing
                else
                    local a, na = xmlevery(c)
                    if na > 0 then
                     -- local mfs = at.mathfractionstack
                     -- if mfs then
                     --     local a = xmlall(c,"/mfrac") or { }
                     --     expandtimesone(a[1],a,1)
                     --     goto DONE
                     -- end
                        local times  = false
                        local block  = true
                        local closed = false
                        for i=1,na do
                            local ai    = a[i]
                            local tg    = ai.tg
                            local at    = ai.at
                            local class = at.mathclass
                            if  at.mathsymbolic
                             or at.mathgroup == "postfix operator"
                            then -- has to come before at.mathfunction check
                                at.checkfunction = "true" -- saves a check later
                                if closed then
                                    at.mathtimes = 9.1
                                    closed = false
                                elseif block then
                                    block = false
                                else
                                    at.mathtimes = 9.2
                                end
                                block = false -- registered functions like f and g
                                times = true
                                expandtimesone(ai,a,i)
                            elseif at.mathfunction
                             -- or at.mathfractionstack -- ADDED
                                or at.mathfunctionstack
                                or class == "integral"
                                or at.mathgroup == "unary set"
                                or at.mathgroup == "number set"
                             -- or at.mathapplication
                            then
                                at.checkfunction = "true" -- saves a check later
                                if closed then
                                    at.mathtimes = 1.1
                                    closed = false
                                elseif block then
                                    block = false
--                                 elseif at.mathgroup == "postfix operator" then
--                                     block = false
                                elseif at.mathgroup ~= "unary set" then
                                    at.mathtimes = 1.2
                                end
                                -- if at.mathsymbolic then
                                --     block = false -- registered functions like f and g
                                -- else
                                block = true -- lim etc .. we need to intercept kind == 1
                                -- end
                                times = false
                                expandtimesone(ai,a,i)
                            elseif tg == "mo" then
                                -- combine more here
                                if class == "open" then
                                    if closed then
                                        at.mathtimes = 2.1
                                        closed = false
                                    elseif times and not block then
                                        at.mathtimes = 2.2
                                    end
                                elseif i > 1 and class == "close" then -- block ?
                                    closed = true
                                else
                                    closed = false
                                end
                                expandtimes(ai)
                                times = false
                                block = true
                            elseif tg == "mn" then
                                if closed then
                                    at.mathtimes = 3.1
                                    closed = false
                                elseif times and not block then
                                    at.mathtimes = 3.2
                                end
                                expandtimesone(ai)
                                times = false
                                block = false
                            elseif tg == "msub" or tg == "msup" or tg == "msubsup" then
                                local first = xmlfirst(ai,"/*")
                                local fat   = first and first.at
                                if closed then
                                    at.mathtimes = 4.1
                                    closed = false
                                elseif first and at.mathclass == "close" then
                                    closed = true
                                elseif times and not block then
                                    at.mathtimes = 4.2
                                end
                                if first then
                                    if     fat.mathfunction
                                        or fat.mathfunctionstack
                                        or fat.mathclass == "integral"
                                        or fat.mathclass == "operator"
                                        or fat.mathclass == "differential"
                                    then
                                        if not fat.mathfunction then
                                            fat.mathfunction = "true" -- maybe also needed for the stack
                                        end
                                        at.checkfunction = "true" -- saves a check later
                                        expandtimesone(ai,a,i)
                                        block = true
                                        times = false
                                    else
                                        expandtimesone(ai,a,i)
                                        block = false
                                        times = true
                                    end
                                else
                                    expandtimesone(ai,a,i)
                                    block = false
                                    times = true
                                end
                            elseif at.mathfractionstack
                                or tg == "mroot"
                                or tg == "mfrac"
                                or tg == "msqrt"
                                or at.mathsymbolic
                            then
                                if closed then
                                    at.mathtimes = 5.1
                                    closed = false
                                elseif block then
                                    block = false
                                else
                                    at.mathtimes = 5.2
                                end
                                times = true
                                expandtimesone(ai,a,i)
                            elseif class == "differential" then
                                closed = false
                                block = true
                                times = true
                                expandtimesone(ai,a,i)
                            elseif class == "variable" then
                             -- local c = at.mathcharacter
                             -- if c == "." or c == "," then -- period comma
                             --     block = true
                             --     times = false
                             -- else
                                    if closed then
                                        at.mathtimes = 6.1
                                        closed = false
                                    elseif times and not block then
                                        at.mathtimes = 6.2
                                    end
                                    block = false
                                    times = true
                             -- end
                                expandtimesone(ai,a,i)
                            elseif tg == "mrow" then
                                if closed or times then
                                    at.mathtimes = 7.1
                                    closed = false
                                end
                                times = true
                                expandtimesone(ai,a,i)
                            elseif tg == "mtext" or tg == "ms" then
                                if closed then
                                    at.mathtimes = 8.1
                                    closed = false
                                end
                                expandtimesone(ai)
                                times = false
                                block = false
                            else
                                if closed then
                                    at.mathtimes = 9.1
                                    closed = false
                                end
                                times = false
                                expandtimesone(ai,a,i)
                            end
                        end
                    end
                end
          ::DONE::
            elseif tg == "mtable" then
                for cell in xmlcollected(c,"mtd") do
                    local a, na = xmlevery(cell)
                    if a then
                        expandtimes(a)
                    end
                end
            elseif tg == "mtext" or tg == "mspace" or tg == "ms" then
                -- nothing to do
            elseif tg == "mfrac" then
                local a, na = xmlevery(c)
                if i > 1 and na == 2 and all[i-1].tg == "mn" and a[1].tg == "mn" and a[2].tg == "mn" and not c.__p__.at.mathfractionstack then
                    -- mark not times
                    c.at.mathplus = 1 -- invisible plus
                end
-- inspect(c.at)
            else
                local a, na = xmlevery(c)
                if a then
                    expandtimes(a)
                end
            end
        end
    end

    expandtimes = function(all)
        if all then
            for i=1,#all do
                expandtimesone(all[i],all,i)
            end
        end
    end

    prepare = function(x)
        local all = xmlall(x,"mi[not @mathfunction]")
        if all then
            for i=1,#all do
                local a = all[i]
                local t = a.dt[1]
                local f = functions[t]
                if f then
                    local at = a.at
                    at.mathfunction = "true"
                    at.mathsymbolic = "true"
                end
            end
        end
        -- left is a function, right is an operator
        for c in xmlcollected(x,"mrow[@mathfunctionstack]") do
--         for c in xmlcollected(x,"(msub|msup|msubsup|mrow)[@mathfunctionstack]") do
            local cat = c.at
            local s   = cat.mathstack
            for cc in xmlcollected(c,"(mi|mo)[@mathstack]") do
                local ccat = cc.at
                if ccat.mathstack == s then
                    cat.mathfunction  = ccat.mathfunction  or cat.mathfunction
                    cat.mathcharacter = ccat.mathcharacter or cat.mathcharacter
                    cat.mathgroup     = ccat.mathgroup     or cat.mathgroup
                    if cc.tg == "mo" then
                        ccat.mathignore  = "true"
                     -- ccat.mathmeaning = cat.mathfunctionstack
                    end
                end
            end
        end
        --
        local all = xmlall(x,"(msub|msup|msubsup)/mo[@mathclass='open' or @mathclass='close' or @mathclass='middle']")
        if all then
            for i=1,#all do
                local ai = all[i]
                local at = ai.at
                at.mathignore = "true"
                ai.__p__.at.mathclass = at.mathclass
            end
        end
        --
        expandtimesone(x)
    end

end

local tomeaning  do

    -- we don't really need to handle mfenced

    local t, n, expand, expandone, okay, language, domain

    -- todo: a getlabel wrapper so that we don't need to pass language and domain

    local function getlabel(tag)
        return getverboselabel(tag,language,domain)
    end

    local function getoptional(tag)
        return getoptionallabel(tag,language,domain)
    end

    local utfsplit  = utf.split
    local utfbyte   = utf.byte
    local isprivate = fonts.helpers.isprivate

    local unknown <const> = " ? "

    local function sanitize(s)
        local t = utfsplit(s)
        local n = #t
        if n == 1 then
            if isprivate(utfbyte(s)) then
                return unknown
            end
        else
            local done = false
            for i=1,n do
                if isprivate(utfbyte(t[i])) then
                    done = true
                    t[i] = unknown
                end
            end
            if done then
                return concat(t)
            end
        end
        return s
    end

    local function expandsymbol(c)
        local at = c.at
        if not at.mathignore then
            local result = at.mathidentity
            if not result then
-- better: mathmeaning as overload
-- if at.mathfunctionstack then
--     result = getlabel(at.mathfunctionstack)
-- else
                local group = at.mathgroup
                if group then
                    local character = tonumber(at.mathcharacter)
                    local index     = tonumber(at.mathindex)
                    local s = getname(group,character) or getname(group,index)
                    if type(s) == "string" then
                        result = getlabel(s)
                    else
                        result = sanitize(xmltext(c))
                    end
                else
                   result = sanitize(xmltext(c))
                end
-- end
            end
            if result ~= "" then
                n = n + 1 ; t[n] = result
            end
        end
    end

    -- replace method by category and kind

    local function hasmiddle(a)
        if a then
            for i=1,#a do
                local class = a[i].at.mathclass
                if class == "middle" then
                    return true
                end
            end
        end
        return false
    end

    local function haslimits(at)
        local f = at.mathfunction
        if f then
            f = functions[f]
            if f then
                return f.method == "limits"
            end
        end
        return false
    end

    local function isintegral(at)
        return at.mathclass == "integral"
    end

    local function isoperator(at)
        return at.mathclass == "operator"
    end

    -- see if we can avoid passing all and i

    local function expandsupindex(c,a,all,i)
        if c.at.mathsupindex then
            n = n + 1 ; t[n] = getlabel("supindex")
            expandone(a,all,i)
        else
            n = n + 1 ; t[n] = getlabel("to the power of")
            expandone(a,all,i)
            local last = t[n]
            if last == "2" then
                n = n - 1 ; t[n] = getlabel("squared")
            elseif last == "3" then
                n = n - 1 ; t[n] = getlabel("cubed")
            end
        end
    end

    local function expandsubindex(c,a,all,i)
        if c.at.mathsubindex then
            n = n + 1 ; t[n] = getlabel("subindex")
        else
            n = n + 1 ; t[n] = getlabel("sub")
        end
        expandone(a,all,i)
    end

    local function checkfunction(a,i,na)
        if i + 3 <= na then
            local a1 = a[i+1]
            if a1.tg == "mo" then
                local a3 = a[i+3]
                if a3.tg == "mo" then
                    local a2 = a[i+2]
                    local t2 = a2.tg
                    if t2 == "mn" or t2 == "mi" then
                        local at1 = a1.at
                        local at3 = a3.at
                        local class1 = at1.mathclass
                        local class2 = at3.mathclass
                        if class1 and class2 and class1 == "open" and class2 == "close" then
                            at1.mathnogroup = "true"
                            at3.mathnogroup = "true"
                        end
                    end
                end
            end
        end
    end

    local partials = {
        ["∂"] = "d", -- todo: bold math
    }

    local function isdifferential(a)
        local a1 = a[1]
        local a2 = a[2]
        if a1 and a2 then
            local tg2 = a2.tg
            local ps = false
            if tg2 == "mrow" or tg2 == "msub" or tg2 == "msup" or tg2 == "msubsup" then
                local aa = xmlfirst(a2,"/*")
                if aa then
                    if aa.tg == "mi" and aa.at.mathclass == "differential" then
                        ps = aa
                    else
                        local aa = xmlfirst(aa,"/*")
                        if aa and aa.tg == "mi" and aa.at.mathclass == "differential" then
                            ps = aa
                        else
                            return false
                        end
                    end
                else
                    return false
                end
            end
            -- can be helper
            local tg1 = a1.tg
            if tg1 == "mrow" or tg1 == "msub" or tg1 == "msup" or tg1 == "msubsup" then
                local aa = xmlfirst(a1,"/*")
                if aa then
                    if aa.tg == "mi" and aa.at.mathclass == "differential" then
                        local p = partials[aa.dt[1]]
                        if p then
                            -- aa.at.mathidentity = p
                            -- ps.at.mathidentity = p
                            return true, true
                        else
                            return true, false
                        end
                    else
                        local aa = xmlfirst(aa,"/*")
                        if aa and aa.tg == "mi" and aa.at.mathclass == "differential" then
                            local p = partials[aa.dt[1]]
                            if p then
                                -- aa.at.mathidentity = p
                                -- ps.at.mathidentity = p
                                return true, true
                            else
                                return true, false
                            end
                        else
                            return false
                        end
                    end
                else
                    return false
                end
            elseif tg1 == "mi" and a1.at.mathclass == "differential" then
                local p = partials[a1.dt[1]]
                if partials[a1.dt[1]] then
                    -- a1.at.mathidentity = p
                    -- ps.at.mathidentity = p
                    return true, true
                else
                    return true, false
                end
            end
        end
        return false
    end

    local function applyof(all,i,label)
        local ai1 = all[i + 1]
        if ai1 and ai1.tg == "mo" then
            local c = ai1.at.mathclass
            if c == "open" then
                n = n + 1 ; t[n] = getlabel(label)
                ai1.at.mathtimes = nil
            end
        end
    end

    local function expandfenced(ai,fenced,all,i)
        local at = ai.at
        local class = at.mathclass
        if not class then
            if at.mathnogroup then
                -- ignore
            else
                expandone(ai,all,i)
            end
        elseif class == "open" then
            if fenced or at.mathnogroup then
                -- ignore
            else
                n = n + 1 ; t[n] = getlabel("begin group")
            end
            at.mathignore = "true"
            expandone(ai)
        elseif class == "close" then
            if fenced or at.mathnogroup then
                -- ignore
            else
                n = n + 1 ; t[n] = getlabel("end group")
            end
            at.mathignore = "true"
            expandone(ai)
        elseif class == "middle" then
            if fenced then
                n = n + 1 ; t[n] = getlabel(fenced.tag .. ":fence")
                at.mathignore = "true"
                expandone(ai)
                local aa = all and all[i+1]
                if aa then
                    aa.at.mathnogroup = "true"
                end
            else
                expandone(ai)
            end
        else
            expandone(ai,all,i)
        end
    end

    local trace_times = false

    trackers.register("structures.tags.math.times", function(v) trace_times = v end)

    expandone = function(c,all,i,start,stop)
        if c then
            local tg = c.tg
            if tg == "mrow" then
                local at = c.at
                if at.mathunit then
                    n = n + 1 ; t[n] = at.mathunit
                else
                    local category = tonumber(at.mathcategory)
                    local kind     = category and functiontype(category)
                    local fenced   = kind == "fence" and functions[category]
                    local a, na    = xmlevery(c)
                    if na > 0 then
                        local subfence = hasmiddle(a)
                        if at.mathfunctionstack then
                            if at.mathfunctionstack then
                                n = n + 1 ; t[n] = getlabel(at.mathfunctionstack)
                            else
                                expandsymbol(c)
                            end
                            at.mathnogroup = "true"
                        else
                            local mfs = at.mathfractionstack -- maybe also check for tg == "mfrac"
                            if mfs then
                                at.mathnogroup = "true"
                        --         fenced = false
                                local s = getlabel(mfs)
                                if na > 1 and s and s ~= "" then -- maybe check if there is a meaning set
                                    at.nofraction = mfs
                                    n = n + 1 ; t[n] = s
                                end
                                local a = xmlall(c,"/mfrac") or { }
                                expandone(a[1],a,1)
                                goto DONE
                            end
                        end
                        -- how about na == 1
                        local isgroup = false
                        if fenced then
                         -- isgroup  = false
                        elseif at.mathnogroup then
                         -- isgroup  = false
                        else
                            isgroup  = n > 1
                        end
                        if fenced then
                            local s = getlabel(fenced.tag)
                            n = n + 1 ; t[n] = getlabel("optional begin")
                            if s == "" then
                                n = n + 1 ; t[n] = getlabel("begin fenced")
                            else
                                n = n + 1 ; t[n] = s
                            end
                        elseif isgroup then
                            n = n + 1 ; t[n] = getlabel(start or "begin group")
                        end
                        if fenced then
                            if na == 3 then
                                a[2].at.mathnogroup = "true"
                            elseif na == 5 and hasmiddle then
                                a[2].at.mathnogroup = "true"
                                a[3].at.mathnogroup = "true"
                            end
                        end
                        --
                        for i=1,na do
                            local ai = a[i]
                            local tg = ai.tg
                            local at = ai.at
                            if at.mathtimes then
                                local s
                             -- if at.mathplus then
                             --     s = getlabel("fractionplus")
                             -- else
                                    s = getlabel("times")
                             -- end
                                if s and s ~= "" then
                                    if trace_times then
                                        n = n + 1 ; t[n] = s .. at.mathtimes
                                    else
                                        n = n + 1 ; t[n] = s
                                    end
                                end
                            end
                            if at.checkfunction then
                                checkfunction(a,i,na)
                                expandone(ai,a,i)
                            elseif tg == "mo" then
                                expandfenced(ai,fenced,a,i)
                         -- elseif tg == "mn" or tg == "mtext" or tg == "ms" then
                         --     expandone(ai)
                            elseif tg == "msub" or tg == "msup" or tg == "msubsup" then
                                local first = xmlfirst(ai,"/*")
                                local fat   = first and first.at
                                if first then
                                    if fat.checkfunction then
                                        checkfunction(a,i,na)
                                        expandone(ai,a,i)
                                    else
                                        expandfenced(ai,fenced,a,i)
                                    end
                                else
                                    expandfenced(ai,fenced,a,i)
                                end
                            else
                                expandone(ai,a,i)
                            end
                        end
                        --
                        if fenced then
                            local s = getlabel(fenced.tag)
                            if s == "" then
                                n = n + 1 ; t[n] = getlabel("end fenced")
                            else
                                n = n + 1 ; t[n] = getlabel("end")
                                n = n + 1 ; t[n] = s
                            end
                        elseif isgroup then
                            n = n + 1 ; t[n] = getlabel(stop or "end group")
                        end
                    end
                end
              ::DONE::
            elseif tg == "mo" then
                expandsymbol(c)
            elseif tg == "mn" then
                n = n + 1 ; t[n] = xmltext(c)
            elseif tg == "mi" then
                local at = c.at
                if at.mathunit then
                    n = n + 1 ; t[n] = at.mathunit
                elseif at.mathsymbolic then
                    n = n + 1 ; t[n] = getlabel("function")
                    expandsymbol(c) -- can be done directly
                    local tg = c.__p__.tg
                    if tg == "msub" or tg == "msup" or tg == "msubsup" then
                    else
                        if all then
                            applyof(all,i,"functionof")
                        end
                    end
                elseif at.mathfunction then
                    n = n + 1 ; t[n] = getlabel(c.dt[1])
                    local tg = c.__p__.tg
                    if tg == "msub" or tg == "msup" or tg == "msubsup" then
                    else
                        if all then
                            applyof(all,i,"functionof")
                        end
                    end
                else
                    local s = getoptional(c.dt[1])
                    if s and s ~= "" then
                        n = n + 1 ; t[n] = s
                    end
                    expandsymbol(c)
                end
            elseif tg == "msub" then
                local a, na = xmlevery(c)
                if a then
                    local a1 = a[1]
                    local at = a1.at
                    expandone(a1,all,i)
                    if haslimits(at) or isintegral(at) or isoperator(at) then
                        if isintegral(at) or isoperator(at) then
                            n = n + 1 ; t[n] = getlabel("integralsub") -- integral with lower limit
                        else
                            n = n + 1 ; t[n] = getlabel("limitsub") -- limit type with lower limit
                        end
                        expandone(a[2],all,i)
                        n = n + 1 ; t[n] = getlabel("pause")
                        if all and #all > i then
                            n = n + 1 ; t[n] = getlabel("operatorof")
                        end
                    else
                        expandsubindex(c,a[2],all,i)
                        if at.mathfunction or at.mathsymbolic then
                            applyof(all,i,"functionof")
                        end
                    end
                end
            elseif tg == "msup" then
                local a, na = xmlevery(c)
                if a then
                    local a1 = a[1]
                    local a2 = a[2]
                    local at = a1.at
                    local group = a2.at.mathgroup
                    if group == "postfix operator" then
                        -- kind of ugly:
                        expandone(a2) -- ignored anyway
                        n = n + 1 ; t[n] = getlabel("operatorof")
                        expandone(a1)
                    elseif group == "prime" then
                        expandone(a1)
                        expandone(a2)
                        if all then
                            applyof(all,i,"primeof")
                        end
                    elseif haslimits(at) or isintegral(at) or isoperator(at) then
                        expandone(a1,all,i)
                        n = n + 1 ; t[n] = getlabel("operatorsup")
                        expandone(a2,all,i)
                        n = n + 1 ; t[n] = getlabel("pause")
                        if all and #all > i then
                            n = n + 1 ; t[n] = getlabel("operatorof")
                        end
                    else
                        expandone(a1,all,i)
                        expandsupindex(c,a2,all,i)
                        if at.mathfunction or at.mathsymbolic then
                            applyof(all,i,"functionof")
                        end
                    end
                end
            elseif tg == "msubsup" then
                local a, na = xmlevery(c)
                if a then
                    local a1 = a[1]
                    local a2 = a[2]
                    local a3 = a[3]
                    local at = a1.at
                    if a2.at.mathclass == "prime" then
                        expandone(a2)
                        expandone(a1)
                        if haslimits(at) or isintegral(at) or isoperator(at) then
                            n = n + 1 ; t[n] = getlabel("operatorsubsupfrom") -- from was: with lower limit
                            expandone(a3,all,i)
                            n = n + 1 ; t[n] = getlabel("pause")
                        end
                        if all and #all > i then
                            n = n + 1 ; t[n] = getlabel("operatorof")
                        end
                    elseif haslimits(at) or isintegral(at) or isoperator(at) then
                        expandone(a1,all,i)
                        n = n + 1 ; t[n] = getlabel("operatorsubsupfrom") -- from was: with lower limit
                        expandone(a2,all,i)
                        n = n + 1 ; t[n] = getlabel("operatorsubsupto")  -- upto was: and upper limit
                        expandone(a3,all,i)
                        n = n + 1 ; t[n] = getlabel("pause")
                        if all and #all > i then
                            n = n + 1 ; t[n] = getlabel("operatorof")
                        end
                    else
                        expandone(a1,all,i)
                        expandsubindex(c,a2,all,i)
                        expandsupindex(c,a3,all,i)
                        if at.mathfunction or at.mathsymbolic then
                            applyof(all,i,"functionof")
                        end
                    end
                end
            elseif tg == "mfrac" then
                local a, na = xmlevery(c)
                if a then
                    local ok, partial = isdifferential(a)
                    if ok then
                        if partial then
                            n = n + 1 ; t[n] = getlabel("the partial derivative")
                        else
                            n = n + 1 ; t[n] = getlabel("the derivative")
                        end
                        expandone(a[1],all,i,"","")
                        n = n + 1 ; t[n] = getlabel("over")
                        expandone(a[2],all,i,"","end derivative")
                    elseif c.__p__.at.nofraction then -- uggly
                        expandone(a[1],all,i,"","")
                        n = n + 1 ; t[n] = getlabel("over")
                        expandone(a[2],all,i,"","")
                        n = n + 1 ; t[n] = getlabel("end " .. c.__p__.at.nofraction)
                        -- n = n + 1 ; t[n] = getlabel(c.__p__.at.nofraction)
                    else
                        n = n + 1 ; t[n] = getlabel("the fraction of")
                        expandone(a[1],all,i,"begin numerator","end numerator")
                        n = n + 1 ; t[n] = getlabel("and")
                        expandone(a[2],all,i,"begin denominator","end denominator")
                    end
                end
            elseif tg == "msqrt" then
                n = n + 1 ; t[n] = getlabel("the square root")
                local a, na = xmlevery(c)
                if a then
                    n = n + 1 ; t[n] = getlabel("rootof")
                    expand(a,all,i)
                end
            elseif tg == "mroot" then
                local a, na = xmlevery(c)
                n = n + 1 ; t[n] = getlabel("the root with degree")
                if a then
                    expandone(a[2],all,i)
                    n = n + 1 ; t[n] = getlabel("rootof")
                    expandone(a[1],all,i)
                end
            elseif tg == "munder" then -- needs checking
                local a, na = xmlevery(c)
                if a then
                    local category = tonumber(c.at.mathcategory)
                    if functiontype(category) == "accent" then
                        local fnc = functions[category]
                        n = n + 1 ; t[n] = getlabel(fnc.tag)
                        expandone(a[1],all,i)
                    else
                        expandone(a[1],all,i)
                        n = n + 1 ; t[n] = getlabel("under")
                        expandone(a[2],all,i)
                    end
                end
            elseif tg == "mover" then -- needs checking
                local a, na = xmlevery(c)
                if a then
                    local category = tonumber(c.at.mathcategory)
                    if functiontype(category) == "accent" then
                        local fnc = functions[category]
                        n = n + 1 ; t[n] = getlabel(fnc.tag)
                        expandone(a[1],all,i)
                    else
                        expandone(a[1],all,i)
                        n = n + 1 ; t[n] = getlabel("over")
                        expandone(a[2],all,i)
                    end
                end
            elseif tg == "munderover" then -- needs checking
                local a, na = xmlevery(c)
                if a then
                    expandone(a[1],all,i)
                    n = n + 1 ; t[n] = getlabel("under")
                    expandone(a[2],all,i)
                    n = n + 1 ; t[n] = getlabel("and over")
                    expandone(a[3],all,i)
                end
            elseif tg == "mtext" then
                n = n + 1 ; t[n] = xmltext(c)
            elseif tg == "mspace" then
                n = n + 1 ; t[n] = ""
            elseif tg == "ms" then
                n = n + 1 ; t[n] = xmltext(c)
            elseif tg == "mmultiscripts" then -- needs checking, todo prime
                local a, na = xmlevery(c)
                if a then
                    local p = false
                    local s = true
                    expandone(a[1],all,i)
                    for i=2,#a do
                        local ai = a[i]
                        if ai.tg == "mprescripts" then
                            p = true
                            n = n + 1 ; t[n] = getlabel("prescripts")
                        elseif p then
                            if ai.tg ~= "mtext" then
                                n = n + 1 ; t[n] = getlabel(s and "presub" or "presuper")
                                expandone(ai,all,i)
                            end
                            s = not s
                        end
                    end
                    s = true
                    for i=2,#a do
                        local ai = a[i]
                        if ai.tg == "mprescripts" then
                            break
                        elseif i == 2 then
                           n = n + 1 ; t[n] = getlabel("postscripts")
                        end
                        if ai.tg ~= "mtext" then
                            n = n + 1 ; t[n] = getlabel(s and "postsub" or "postsuper")
                            expandone(ai,all,i)
                        end
                        s = not s
                    end
                    n = n + 1 ; t[n] = getlabel("end scripts")
                end
            elseif tg == "math" then
                local a, na = xmlevery(c)
                if a then
                    expand(a,all,i)
                end
            elseif tg == "mtable" then
                local detail = c.at.detail
                if detail == "cases" then
                    n = n + 1 ; t[n] = getlabel("begin cases")
                    local nr = 0
                    for row in xmlcollected(c,"/mtr") do
                        nr = nr + 1
                        n = n + 1 ; t[n] = getlabel("case")
                        n = n + 1 ; t[n] = tostring(nr)
                        for cell in xmlcollected(row,"/mtd") do
                            local a, na = xmlevery(cell)
                            if a then
                                expand(a)
                            end
                        end
                     -- n = n + 1 ; t[n] = getlabel("end case")
                    end
                    n = n + 1 ; t[n] = getlabel("end cases")
                else
                    n = n + 1 ; t[n] = getlabel("begin table")
                    local nr = 0
                    for row in xmlcollected(c,"/mtr") do
                        local nc = 0
                        nr = nr + 1
                     -- n = n + 1 ; t[n] = getlabel("row")
                     -- n = n + 1 ; t[n] = tostring(nr)
                        for cell in xmlcollected(row,"/mtd") do
                            nc = nc + 1
                         -- n = n + 1 ; t[n] = getlabel("column")
                         -- n = n + 1 ; t[n] = tostring(nc)
                            n = n + 1 ; t[n] = getlabel("cell")
                            n = n + 1 ; t[n] = tostring(nr)
                            n = n + 1 ; t[n] = tostring(nc)
                            local a, na = xmlevery(cell)
                            if a then
                                expand(a)
                            end
                        end
                    end
                    n = n + 1 ; t[n] = getlabel("end table")
                end
            elseif not c.special then
                okay = false
                report("todo: %s",tostring(c))
            end
        end
    end

    expand = function(all)
        if all then
            for i=1,#all do
                expandone(all[i],all,i)
            end
        end
    end

    local keep_last = false

    trackers.register("structures.tags.math.keeplast", function(v) keep_last = v end)

    tomeaning = function(x,l)
        t        = { }
        n        = 0
        okay     = true
        language = l or "en"
        domain   = x.at["data-lmtx-domain"] or x.at["domain"] or "default"
        --
        expandone(x)
        if okay then
            -- we can have a helper for this
            local m = false
            for i=1,n do
                local ti = t[i]
                if ti == "" then
                    if not m then
                        m = i - 1
                    end
                elseif m then
                    m = m + 1
                    t[m] = ti
                end
            end
            if m then
                n = m
            end
            if t[n] == "." then
                n = n - 1
            end
            if keep_last then
                xmlfixattributes(x)
                buffers.assign(type(keep_last) == "string" and keep_last or "lastmms",tostring(x))
            else
                -- maybe wipe the buffer
            end
            return concat(t," ",1,n)
        end
    end

end

local stripped  do

    local strip = true

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

    stripped = function(s)
        if strip and #s > 0 then
-- print(s)
            local x = xmlconvert(s)
            xmlwipeattributes(x)
            return tostring(x)
        else
            return s
        end
    end

end

local verbose  do

    -- We could save the prepared if we do more languages but that is only relevant when
    -- we develop so it has little gain.

    local warned = false
 -- local saved  = false
 -- local saving = false

 -- trackers.register("structures.tags.math.save",function(v)
 --     saving = v
 --     if saving and not saved then
 --         saved = table.setmetatableindex("table")
 --         luatex.registerstopactions(function() table.save(tex.jobname .. "-mms-meanings.lua",saved) end)
 --     end
 -- end)

    verbose = function(s,l)
        if not warned then
            report("this feature is experimental and under construction")
            warned = true
        end
        local root = xmlconvert(s)
        if not root.error then
            local x = xmlfirst(root,"/math")
            if x then
                prepare(x)
-- inspect(x)
                local meaning = tomeaning(x,l)
             -- if saving then
             --     saved[s][l] = meaning
             -- end
                return meaning
            end
        end
    end

end

xml.mml = {
    verbose  = verbose,
    stripped = stripped,
}
