back-exp-imp-mth.lmt /size: 47 Kb    last modification: 2025-02-21 11:03
1if not modules then modules = { } end modules ['back-exp-imp-mth'] = {
2    version   = 1.001,
3    comment   = "companion to back-exp.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9local sub, gsub = string.sub, string.gsub
10local utfchar, utfvalues = utf.char, utf.values
11local insert = table.insert
12local setmetatableindex, concat = table.setmetatableindex, table.concat
13
14local structurestags = structures.tags
15local specifications = structurestags.specifications
16local locatedtag     = structurestags.locatedtag
17
18local backend        = structurestags.backend
19
20local setattribute   = backend.setattribute
21local extras         = backend.extras
22local checks         = backend.checks
23local finalizers     = backend.finalizers
24
25----- bpfactor       = number.dimenfactors.bp
26----- f_points       = string.formatters["%p"]
27local f_em           = string.formatters["%.6Nem"]
28
29local implement      = interfaces.implement
30
31local mathmlns       <const> = "http://www.w3.org/1998/Math/MathML"
32
33do
34
35    ----- automathrows   = true  directives.register("export.math.autorows",   function(v) automathrows   = v end)
36    ----- automathapply  = true  directives.register("export.math.autoapply",  function(v) automathapply  = v end)
37    ----- automathnumber = true  directives.register("export.math.autonumber", function(v) automathnumber = v end)
38    local automathstrip  = true  directives.register("export.math.autostrip",  function(v) automathstrip  = v end)
39
40    local functions      = mathematics.categories.functions
41    local tagging        = mathematics.tagging
42    local functiontype   = mathematics.functiontype
43
44    local function collapse(di,i,data,ndata,detail,element)
45        local collapsing = di.data
46        if data then
47            di.element = element
48            di.detail = nil
49            i = i + 1
50            while i <= ndata do
51                local dn = data[i]
52                if dn.detail == detail then
53                    collapsing[#collapsing+1] = dn.data[1]
54                    dn.skip = "ignore"
55                    i = i + 1
56                else
57                    break
58                end
59            end
60        end
61        return i
62    end
63
64    local function collapse_all_mn(data)
65        local n = #data
66        local f = false
67        local l = false
68        local d = false
69        local p = false
70        for i=1,n do
71            local di = data[i]
72            local tg = di.tg
73            if tg == "mn" then
74                if not f then
75                    d = di.data
76                    f = i
77                    p = false
78                else
79                    if p then
80                        p.element = "mn"
81                        local s = specifications[p.fulltag]
82                        s.mathcharacter = nil
83                        s.mathclass     = nil
84                        s.mathgroup     = nil
85                        s.mathindex     = nil
86                    end
87                    d[#d+1] = di.data[1]
88                    data[i] = false
89                    p = false
90                end
91                l = i
92            elseif tg == "mi" or tg == "mc" then
93                if p then
94                    f = false
95                end
96                if i < n and data[i+1].tg == "mn" then
97                    local d1 = di.data[1]
98                    if d1 then
99                        local c = d1.content
100                        local s = specifications[di.fulltag]
101                        if s.mathclass == "ordinary" and c and (c == "." or c == ",") then
102                            if not f then
103                                d = di.data
104                                f = i
105                                l = i
106                                p = di
107                            else
108                                d[#d+1] = d1
109                                data[i] = false
110                            end
111                        else
112                            f = false
113                        end
114                    else
115                        f = false
116                    end
117                else
118                    f = false
119                end
120            elseif f and (tg == "msub" or tg == "msup" or tg == "msubsup") then
121                local d1 = di.data[1]
122                if d1 and d1.tg == "mn" then
123                    d[#d+1] = d1.data[1]
124                    d1.data = d
125                    data[f] = false
126                end
127                f = false
128            else
129                f = false
130            end
131        end
132        if d then
133            local j = 0
134            for i=1,n do
135                local d = data[i]
136                if d then
137                    j = j + 1
138                    data[j] = d
139                end
140            end
141            for i=j+1,n do
142                data[i] = nil
143            end
144        end
145    end
146
147    local functioncontent = { }
148
149    setmetatableindex(functioncontent,function(t,k)
150        local v = { { content = k } }
151        t[k] = v
152        return v
153    end)
154
155    local dummy_nucleus = {
156        element   = "mtext",
157        data      = { content = "" },
158        nature    = "mixed",
159        comment   = "dummy nucleus",
160        fulltag   = "mtext>0"
161    }
162
163    local function accentchar(d) -- see mkiv for old one
164        local detail = tonumber(d.detail)
165        if detail then
166            local d1 = d.data[1]
167            if d1 and d1.tg == "mrow" then
168                --
169                d.element = "mrow" -- used
170                d.detail = nil
171                local s = specifications[d.fulltag] -- hm, used when not set
172                s.detail = nil
173                --
174                d1.element = "mo" -- used
175             -- d1.detail = nil
176                d1.attributes = { stretchy = "true" }
177                d1.nature = "mixed"
178                d1.data = { { content = utfchar(detail) } }
179                return d
180            end
181        end
182    end
183
184    local function maybetext(d)
185        if #d.data == 1 and d.data[1].content then
186            d.element = "mtext"
187        else -- maybe also check if d.data[1].content
188            d.element = "mrow"
189        end
190        return d
191    end
192
193    local no_mrow = {
194
195        math          = true,
196
197        mrow          = true,
198        mfenced       = true,
199        mfrac         = true,
200        mroot         = true,
201        msqrt         = true,
202        mtable        = true,
203        mi            = true,
204        mo            = true,
205        mn            = true,
206        mspace        = true,
207        mtext         = true,
208
209     -- mmultiscripts = true,
210     -- mstacker      = true,
211     -- mextensible   = true,
212     -- mdelimited    = true,
213    }
214
215    -- we could make a stupid xml and then just use lpath to clean it up
216
217    -- local separator <const> = utfchar(0x2061)
218
219    -- local function markapplicationof(di,data,i)
220    --     if i > 1 then
221    --         local d0 = data[i-1]
222    --         local at = d0.attributes
223    --         if at then
224    --             at.mathapplication = true
225    --         else
226    --             d0.attributes = {
227    --                 mathapplication = true,
228    --             }
229    --         end
230    --     end
231    --     -- di.tg = "ignore"
232    --     -- di.skip = "ignore"
233    -- end
234
235    local function checked(d,n)
236        if n == 1 then
237            local di = d[1]
238            if di then
239                local tg = di.tg
240                if tg == "ignore" then
241                    -- todo: we can move ignore's data one level up
242                    return 1
243                elseif di.content then
244                    return 1
245                else
246                    local dd = di.data
247                    local nn = #dd
248                    if nn > 0 and checked(dd,nn) > 0 then
249                        return 1
250                    else
251                        return 0
252                    end
253                end
254            else
255                -- kind of weird
256                return 0
257            end
258        else
259            local m = 0
260            for i=1,n do
261                local di = d[i]
262                if di then
263                    local tg = di.tg
264                    if tg == "ignore" then
265                        -- skip
266                    elseif di.content then
267                        m = m + 1
268                        d[m] = di
269                    else
270                        local dd = di.data
271                        local nn = #dd
272                        if nn > 0 and checked(dd,nn) > 0 then
273                            m = m + 1
274                            d[m] = di
275                        end
276                    end
277                else
278                    -- kind of weird
279                end
280            end
281            if m < n then
282                for i=n,m+1,-1 do
283                    d[i] = nil
284                end
285            end
286            return m
287        end
288    end
289
290    local stretched = {
291        operator = "false",
292        open     = "false",
293        close    = "false",
294        middle   = "false",
295        integral = "false",
296    }
297
298    local nbsp <const> = utfchar(0x00A0) -- &#0160;
299    local mbsp <const> = utfchar(0x2000) -- after : ; ,
300
301--     ," ",nbsp
302
303    local replacer = lpeg.replacer {
304        [", "] = ",".. mbsp,
305        [". "] = ".".. mbsp,
306        [": "] = ":".. mbsp,
307        ["; "] = ";".. mbsp,
308        [","]  = ",".. mbsp,
309        ["."]  = ".".. mbsp,
310        [":"]  = ":".. mbsp,
311        [";"]  = ";".. mbsp,
312        [" "]  =       nbsp,
313    }
314
315
316    local function checkmath(root) -- we can provide utf.toentities as an option
317        local data = root.data
318        if data then
319            local ndata = #data
320            local roottg = root.tg
321            if roottg == "mo" then
322                local s         = specifications[root.fulltag]
323                local class     = s.mathclass
324                local group     = s.mathgroup
325                local delimiter = s.delimiter
326-- why not print(s.accent)
327                if class or group then
328                    root.attributes = {
329                        mathclass     = class,
330                        mathgroup     = group,
331                        mathindex     = s.mathindex,
332                        mathcharacter = s.mathcharacter,
333                        mathstack     = s.mathstack,
334                        stretchy      = (delimiter == "true" and "true") or stretched[class] or nil,
335                    }
336                else
337                    -- todo: stackers, accents etc
338-- we can put an accent class on the char
339                    root.attributes = {
340                        mathstack = s.mathstack,
341                     -- stretchy  = "false",
342                        stretchy  = delimiter == "true" and "true" or "false", -- too much now
343                    }
344                end
345            elseif roottg == "mfenced" then
346                local s = specifications[root.fulltag]
347                local o = s.operator
348                if o then
349                    root.skip = "comment"
350                 -- root.content = utfchar(o) -- use embedded for now
351                elseif tagging.mfenced then
352                    local l = s.left
353                    local m = s.middle
354                    local r = s.right
355                    if l then
356                        l = utfchar(l)
357                    end
358                    if m then
359                        local t = { }
360                        for i=1,#m do
361                            t[i] = utfchar(m[i])
362                        end
363                        m = concat(t)
364                    end
365                    if r then
366                        r = utfchar(r)
367                    end
368                    root.attributes = {
369                        open       = l,
370                        separators = m,
371                        close      = r,
372                        stretchy   = "true",
373                    }
374                else
375                    root.element    = "mrow"
376                    root.attributes = {
377                        mathcategory = s.mathcategory,
378                     -- stretchy     = "true", -- not needed, more a signal
379                    }
380                end
381         -- elseif roottg == "mstacker" then
382            elseif roottg == "mrow" then
383                ndata = checked(data,ndata)
384            end
385            if ndata == 0 then
386                root.skip = "comment" -- get rid of weird artefacts
387                root.nota = "weird"
388                return
389            elseif ndata == 1 then
390                local d = data[1]
391                if not d or d == "" then
392                    root.skip = "comment"
393                    return
394                elseif d.content then
395                    return
396                elseif roottg == "mrow" or roottg == "mtext" then
397                    -- maybe just always ! check spec first
398                    -- or we can have checks.* for each as we then can flatten
399                    local s = specifications[root.fulltag]
400                    if s.mathunit then
401                        d.attributes = {
402                            mathunit = s.mathunit
403                        }
404                    elseif s.mathdigits then
405                        d.attributes = {
406                            mathdigits = s.mathdigits
407                        }
408                    elseif s.mathfunction then
409                        d.attributes = {
410                           mathfunction = s.mathfunction,
411                           mathcategory = s.mathcategory,
412                           mathstack    = s.mathstack,
413                        }
414                    elseif no_mrow[d.tg] then
415                        root.skip = "comment"
416                    else
417                        d.attributes = {
418                           mathstack = s.mathstack,
419                        }
420                    end
421                elseif roottg == "mo" then
422                    if d.tg == "mo" then
423                        root.skip = "comment"
424                    end
425                end
426            end
427            -- todo
428            local i = 1
429            while i <= ndata do                   -- -- -- TOO MUCH NESTED CHECKING -- -- --
430                local di = data[i]
431                if di and not di.content then
432                    local tg = di.tg
433                    if tg == "math" then
434                     -- di.element = "mrow" -- when properties
435                        di.skip = "comment"
436                        checkmath(di)
437                        i = i + 1
438                    elseif tg == "mover" then
439                        local s = specifications[di.fulltag]
440                        if s.accent then
441                            local t = s.top
442                            local d = di.data
443                            -- todo: accent = "false" (for scripts like limits)
444                            di.attributes = {
445                                accent       = "true",
446                                mathcategory = s.mathcategory,
447                            }
448                            -- todo: p.topfixed
449                            if t then
450                                -- mover
451                                if true then
452                                    local dd = d[1].data
453                                    if dd then
454                                        dd[1].content = utfchar(t)
455                                    end
456                                end
457                                --
458                                di.data = { d[2], d[1] }
459                            end
460                        else
461                            -- can't happen
462                        end
463                        checkmath(di)
464                        i = i + 1
465                    elseif tg == "munder" then
466                        local s = specifications[di.fulltag]
467                        if s.accent then
468                            local b = s.bottom
469                            local d = di.data
470                            -- todo: accent = "false" (for scripts like limits)
471                            di.attributes = {
472                                accent       = "true",
473                                mathcategory = s.mathcategory,
474                            }
475                         -- todo: p.bottomfixed
476                            if b then
477                                -- munder
478                                if true then
479                                    local dd = d[2].data
480                                    if dd then
481                                        dd[1].content = utfchar(b)
482                                    end
483                                end
484                            end
485                        else
486                            -- can't happen
487                        end
488                        checkmath(di)
489                        i = i + 1
490                    elseif tg == "munderover" then
491                        local s = specifications[di.fulltag]
492                        if s.accent then
493                            local t = s.top
494                            local b = s.bottom
495                            local d = di.data
496                            -- todo: accent      = "false" (for scripts like limits)
497                            -- todo: accentunder = "false" (for scripts like limits)
498                            di.attributes = {
499                                accent      = "true",
500                                accentunder = "true",
501                            }
502                         -- todo: p.topfixed
503                         -- todo: p.bottomfixed
504                            if t and b then
505                                -- munderover
506                                if true then -- we don't go here any more
507                                    local dd = d[1].data
508                                    if dd then
509                                        dd[1].content = utfchar(t)
510                                    end
511                                    local dd = d[3].data
512                                    if dd then
513                                        dd[1].content = utfchar(b)
514                                    end
515                                end
516                                di.data = { d[2], d[3], d[1] }
517                            else
518                                -- can't happen
519                            end
520                        else
521                            -- can't happen
522                        end
523                        checkmath(di)
524                        i = i + 1
525                    elseif tg == "mstacker" then
526                        local d = di.data
527                        local d1 = d[1]
528                        local d2 = d[2]
529                        local d3 = d[3]
530                        local t1 = d1 and d1.tg
531                        local t2 = d2 and d2.tg
532                        local t3 = d3 and d3.tg
533                        local m  = nil -- d1.data[1]
534                        local t  = nil
535                        local b  = nil
536                        -- only accent when top / bot have stretch
537                        -- normally we flush [base under over] which is better for tagged pdf
538                        if t1 == "mstackermid" then
539                            if t2 == "mstackertop" then
540                                if t3 == "mstackerbot" then
541                                    m = accentchar(d1) or maybetext(d1)-- or m
542                                    t = accentchar(d2) or maybetext(d2)
543                                    b = accentchar(d3) or maybetext(d3)
544                                    di.element = "munderover"
545                                    di.data    = { m or d1.data[1], b, t }
546                                else
547                                    m = accentchar(d1) or maybetext(d1)-- or m
548                                    t = accentchar(d2) or maybetext(d2)
549                                    di.element = "mover"
550                                    di.data    = { m or d1.data[1], t }
551                                end
552                            elseif t2 == "mstackerbot" then
553                                if t3 == "mstackertop" then
554                                    m = accentchar(d1) or maybetext(d1)-- or m
555                                    b = accentchar(d2) or maybetext(d2)
556                                    t = accentchar(d3) or maybetext(d3)
557                                    di.element = "munderover"
558                                    di.data    = { m or d1.data[1], t, b  }
559                                else
560                                    m = accentchar(d1) or maybetext(d1)-- or m
561                                    b = accentchar(d2) or maybetext(d2)
562                                    di.element = "munder"
563                                    di.data    = { m or d1.data[1], b }
564                                end
565                            else
566                                m = accentchar(d1) -- or m
567                                di.element = "mrow"
568                                di.data    = { m or d1.data[1] }
569                            end
570                        end
571                        if t or b then
572                            di.attributes = {
573                                accent      = t and "true" or nil,
574                                accentunder = b and "true" or nil,
575                            }
576                            di.detail = nil
577                        end
578                        checkmath(di)
579                        i = i + 1
580                    elseif tg == "mroot" then
581                        local data = di.data
582                        local size = #data
583                        if size == 1 then
584                            -- else firefox complains ... code in math-tag (for pdf tagging)
585                            di.element = "msqrt"
586                        elseif size == 2 then
587                            data[1], data[2] = data[2], data[1]
588                        end
589                        checkmath(di)
590                        i = i + 1
591                    elseif tg == "mdelimited" then
592                        local d = di.data
593                        local n = #d
594                        for i=1,n do
595                            local di = d[i]
596                            local properties = specifications[di.fulltag] or { }
597                            local location = properties.delimiterlocation
598                            if location then
599                                if di.attributes then
600                                    di.attributes.delimiterlocation = location
601                                else
602                                    -- useless and bad anyway
603                                    di.attributes = { delimiterlocation = location }
604                                end
605                            end
606                        end
607                        di.element = "mrow"
608                        checkmath(di)
609                        i = i + 1
610                    elseif tg == "mstack" then
611                        di.element = "mover"
612                        local scriptlevel  = "1" -- "+1"
613                        if di.attributes then
614                            di.attributes.scriptlevel = scriptlevel
615                        else
616                            di.attributes = { scriptlevel = scriptlevel }
617                        end
618                        checkmath(di)
619                        i = i + 1
620                    elseif tg == "break" then
621                        di.skip = "comment"
622                        i = i + 1
623                    elseif tg == "mspace" then
624                     -- di.empty = true
625                        local s = specifications[di.fulltag]
626                        local e = s and s.emfactor
627                        if e and e ~= 0 then
628                            di.element = "mspace"
629                            di.attributes = {
630                                width = f_em(e),
631                            }
632                        end
633                        i = i + 1
634                    elseif tg == "mtext" then
635                        -- this is only needed for unboxed mtexts ... all kind of special
636                        -- tex border cases and optimizations ... trial and error
637                        local data = di.data
638                        local size = #data
639-- bmatrix ...
640                        if size > 1 then
641                            for i=1,size do
642                                local di = data[i]
643                                local content = di.content
644                                if content then
645                                    data[i] = {
646                                        element = "mtext",
647                                        nature  = "inline",
648                                        data    = { di },
649                                        n       = 0,
650                                    }
651--                                     di.content = gsub(content," ",nbsp)
652di.content = lpeg.match(replacer,content)
653                                elseif di.tg == "math" then
654                                    local di = di.data[1]
655                                    if di then
656                                        data[i] = di
657                                        checkmath(di)
658                                    end
659                                end
660                            end
661                            di.element = "mrow"
662                         -- di.tg = "mrow"
663                         -- di.nature  = "inline"
664                        elseif size > 0 then
665                            local di = data[1]
666                            local content = di.content
667                            if content then
668--                                 di.content = gsub(content," ",nbsp)
669di.content = lpeg.match(replacer,content)
670                            end
671                        end
672                        checkmath(di)
673                        i = i + 1
674                    elseif tg == "mi" then
675                        local s = specifications[di.fulltag]
676                        if s.mathclass == "punctuation" then
677                            di.element = "mtext"
678                            checkmath(di)
679                            i = i + 1
680                        else
681                         -- if di.data[1].content == separator then
682                         --     markapplicationof(di,data,i)
683                         -- end
684                            local class    = s.mathclass
685                            local group    = s.mathgroup
686                         -- local category = s.mathcategory
687                            if class or group then
688                                di.attributes = {
689                                    mathclass     = class,
690                                    mathgroup     = group,
691                                    mathindex     = s.mathindex,
692                                    mathcharacter = s.mathcharacter,
693                                    mathstack     = s.mathstack,
694                                }
695                            else
696                                di.attributes = {
697                                    mathstack     = s.mathstack,
698                                }
699                            end
700                            checkmath(di)
701                            i = i + 1
702                        end
703                    elseif tg == "mrow" then
704                        local d = di.data
705                        local n = #d
706                        local s = specifications[di.fulltag]
707                        if s.mathunit then
708                            di.attributes = { mathunit = s.mathunit }
709                            i = i + 1
710                        elseif s.mathdigits then
711                            -- does this ever happen (if so check)
712                            for i=1,n do
713                                d[i] = d[i].data[1].content or ""
714                            end
715                            di.tg      = "mn"
716                            di.element = "mn"
717                            di.nature  = "mixed"
718                            di.data    = { { content = concat(d) } }
719                            i = i + 1
720                        elseif s.mathfunction then
721                            di.attributes = {
722                                mathfunction = s.mathfunction,
723                                mathstack    = s.mathstack
724                            }
725                            local category = tonumber(s.mathcategory)
726                            if functiontype(category) == "function" then
727                                local fnc = functions[category] -- functions[s.mathfunction]
728                                local tag = fnc and fnc.tag
729                                if tag then
730                                    di.tg      = "mi"
731                                    di.element = "mi"
732                                    di.nature  = "mixed"
733                                    di.data    = functioncontent[tag]
734                                end
735                            end
736                            i = i + 1
737                        elseif s.mathfunctionstack then
738                            di.attributes = {
739                                mathfunction      = s.mathfunction,
740                                mathstack         = s.mathstack,
741                                mathfunctionstack = s.mathfunctionstack,
742                            }
743                            checkmath(di)
744                            i = i + 1
745                        elseif s.mathdelimitedstack then
746                            di.attributes = {
747                                mathdelimitedstack = s.mathdelimitedstack,
748                            }
749                            checkmath(di)
750                            i = i + 1
751                        elseif s.mathfractionstack then
752                            di.attributes = {
753                                mathfractionstack = s.mathfractionstack,
754                            }
755                            checkmath(di)
756                            i = i + 1
757                        else
758                            if n > 0 then
759                                local d1 = d[1]
760                                local tg = d1.tg
761                                if tg == "mfrac" then
762                                    if n == 1 then -- simple one
763                                        di = d1
764                                        data[i] = di
765                                    elseif n == 2 and d[2].tg == "ignore" then -- simple one with nilled middle fence
766                                        -- already wiped anyway
767                                        di = d1
768                                        data[i] = di
769                                    end
770--                                 elseif tg == "mo" then
771-- if i > 1 and d1.data[1].content == separator then
772--     markapplicationof(di,data,i)
773-- end
774                                end
775                            end
776                            checkmath(di)
777                            i = i + 1
778                        end
779                    elseif tg == "formulacaption" then -- formulanumber
780                        di.tg = "ignore"
781                        i = i + 1
782                    elseif tg == "subformula" then
783                        di.tg = "mrow"
784                        checkmath(di)
785                        i = i + 1
786                    else
787                        -- or first check for tg
788                        local s = specifications[di.fulltag]
789                        if not s then
790                            -- whatever
791                        elseif tg == "mfrac" then
792                            if s.mathfractionrule == "no" then
793                                di.attributes = {
794                                    rulethickness = "0"
795                                }
796                            end
797                        elseif tg == "msup" then
798                            if s.limits then
799                                di.element = "mover"
800                            end
801                        elseif tg == "msub" then
802                            if s.limits then
803                                di.element = "munder"
804                            end
805                        elseif tg == "msubsup" then
806                            if s.limits then
807                                di.element = "munderover"
808                            end
809                        end
810                        --
811                        checkmath(di)
812                        i = i + 1
813                    end
814                else -- can be string or boolean
815                    if parenttg ~= "mtext" and di == " " then
816                        data[i] = false
817                    end
818                    i = i + 1
819                end
820            end
821        end
822    end
823
824    local function stripmath(di)
825        if not di then
826            --
827        elseif di.content then
828            return di
829        else
830            local tg = di.tg
831            if tg == "mtext" or tg == "ms" then
832                return di
833            elseif tg == "mspace" then
834                return di
835            else
836                local data = di.data
837                local ndata = #data
838                local n = 0
839                for i=1,ndata do
840                    local d = data[i]
841                    if d and not d.content then
842                        d = stripmath(d)
843                    end
844                    if d then
845                        local content = d.content
846--                         if d.tg == "mspace" then
847--                             n = n + 1
848--                             data[n] = d
849--                             d.data = { }
850--                         elseif not content then
851--                             n = n + 1
852--                             data[n] = d
853--                             d.__i__ = n -- hm
854--                         else
855--                             n = n + 1
856--                             data[n] = d
857--                         end
858                        n = n + 1
859                        data[n] = d
860                        if d.tg == "mspace" then
861                            d.data = { }
862                        elseif not content then
863                            d.__i__ = n -- hm
864                        end
865                    end
866                end
867                for i=ndata,n+1,-1 do
868                    data[i] = nil
869                end
870                -- maybe integrate the above and this one:
871                collapse_all_mn(data)
872                --
873                if #data > 0 then
874                    return di
875                end
876            end
877        end
878    end
879
880    function checks.math(di)
881-- inspect(di)
882        if di.skip == "comment" then
883            -- already done, kind of weird, happens in mathmatrix, maybe some collapse
884            -- issue that i need to look into
885        else
886            local specification = specifications[di.fulltag]
887            local mode   = specification.mode == "display" and "block" or "inline"
888            local domain = specification.domain or "default"
889            local family = specification.family or "regular"
890            local style  = specification.style or 2 -- text
891            di.attributes = {
892             -- ["xmlns:m"]          = mathmlns,
893                ["xmlns"]            = mathmlns,
894                ["display"]          = mode,
895                ["alttext"]          = specification.input, -- bonus, will go
896             -- ["class"]            = style < 2 and ".math_display_style" or ".math_text_style",
897                ["displaystyle"]     = style < 2 and "true" or "false",
898                ["data-lmtx-blob"]   = specification.blob,
899                ["data-lmtx-domain"] = domain ~= "default" and domain or nil,
900                ["data-lmtx-family"] = family ~= "regular" and family or nil,
901            }
902            -- can be option if needed:
903            if specification.standalone then
904                di.nature = "display"
905            elseif mode == "inline" then
906             -- di.nature = "mixed"  -- else spacing problem (maybe inline)
907                di.nature = "inline" -- we need to catch x$X$x and x $X$ x
908            else
909                di.nature = "display"
910            end
911            if automathstrip then
912                stripmath(di)
913            end
914            checkmath(di)
915        end
916    end
917
918    -- this one can replace some of the previous code .. todo (test on mathmatrix)
919
920    local mprescripts = {
921        element = "mprescripts",
922        data    = { content = "" },
923        nature  = "inline", -- mixed
924        fulltag = "mprescripts>0"
925    }
926
927    local function wrapup(d,index,n,prelist,kernel,postlist)
928        local di   = d[index]
929        local post = #postlist
930        local pre  = #prelist
931        local list = { kernel }
932        local size = 1
933        for i=index+1,n do
934            d[i].skip = "ignore"
935        end
936        if post > 0 then
937            for i=1,post do
938                size = size + 1 ; list[size] = postlist[i]
939            end
940        end
941        if pre > 0 then
942            size = size + 1 ; list[size] = mprescripts
943            for i=1,pre do
944                size = size + 1 ; list[size] = prelist[i]
945            end
946        end
947        di.element        = "mmultiscripts"
948     -- di.tg             = "mmultiscripts"
949        di.data           = list
950        di.iscontinuation = true
951        di.__i__          = size
952    end
953
954    local function found(d,what)
955        if what then
956            for i=1,#d do
957                local di = d[i]
958                local sp = specifications[di.fulltag]
959                if sp and sp.script == what then
960                    return di
961                end
962            end
963        else
964            for i=1,#d do
965                local di = d[i]
966                local sp = specifications[di.fulltag]
967                if not sp or not sp.script then
968                    return di
969                end
970            end
971        end
972    end
973
974    -- todo: pick up kernel
975
976    function checks.mrow(di)
977        local d = di.data
978        if d then
979            local postlist = nil
980            local prelist  = nil
981            local kernel   = nil
982            local index    = 0
983            for i=1,#d do
984                local di   = d[i]
985                local data = di.data
986                local tg   = di and di.tg
987                local sp   = specifications[di.fulltag]
988                local continuation = sp and sp.continuation
989                if continuation then
990                    -- head kernel next
991                    if postlist and continuation.head then
992                        wrapup(d,index,#d,prelist,kernel,postlist)
993                        postlist = nil
994                        prelist  = nil
995                        kernel   = 0
996                    end
997                    -- play safe as we can have empty ones (so maybe move the extra code here)
998                    local nd = #data
999                    if tg == "msup" then
1000                        if not postlist then
1001                            index    = i
1002                            postlist = { }
1003                            prelist  = { }
1004                            kernel   = dummy_nucleus
1005                        end
1006                        local sup = found(data,"sup")
1007                        postlist[#postlist+1] = dummy_nucleus
1008                        postlist[#postlist+1] = sup
1009                        if continuation.kernel then
1010                            kernel = data[1] or dummy_nucleus
1011                        end
1012                    elseif tg == "msub" then
1013                        if not postlist then
1014                            index    = i
1015                            postlist = { }
1016                            prelist  = { }
1017                            kernel   = dummy_nucleus
1018                        end
1019                        local sub = found(data,"sub")
1020                        postlist[#postlist+1] = sub
1021                        postlist[#postlist+1] = dummy_nucleus
1022                        if continuation.kernel then
1023                            kernel = data[1] or dummy_nucleus
1024                        end
1025                    elseif tg == "msubsup" then
1026                        if not postlist then
1027                            index    = i
1028                            postlist = { }
1029                            prelist  = { }
1030                            kernel   = dummy_nucleus
1031                        end
1032                        local sub = found(data,"sub")
1033                        local sup = found(data,"sup")
1034                        postlist[#postlist+1] = sub or dummy_nucleus
1035                        postlist[#postlist+1] = sup or dummy_nucleus
1036                        if continuation.kernel then
1037                            kernel = data[1] or dummy_nucleus
1038                        end
1039                    elseif tg == "mmultiscripts" then
1040                        -- we know that we only have one set
1041                        if not postlist then
1042                            index    = i
1043                            postlist = { }
1044                            prelist  = { }
1045                            kernel   = dummy_nucleus
1046                        end
1047                        local prime  = found(data,"prime")
1048                        local sub    = found(data,"sub")
1049                        local sup    = found(data,"sup")
1050                        local presub = found(data,"presub")
1051                        local presup = found(data,"presup")
1052                        if prime then
1053                            postlist[#postlist+1] = dummy_nucleus
1054                            postlist[#postlist+1] = prime
1055                        end
1056                        if sub or sup then
1057                            postlist[#postlist+1] = sub or dummy_nucleus
1058                            postlist[#postlist+1] = sup or dummy_nucleus
1059                        end
1060                        if presub or presup then
1061                            prelist[#prelist+1] = presub or dummy_nucleus
1062                            prelist[#prelist+1] = presup or dummy_nucleus
1063                        end
1064                        if continuation.kernel then
1065                            kernel = data[1] or dummy_nucleus
1066                        end
1067                    else
1068                        postlist = nil
1069                        prelist  = nil
1070                        kernel   = nil
1071                    end
1072                elseif postlist then
1073                    wrapup(d,index,i-1,prelist,kernel,postlist)
1074                    postlist = nil
1075                    prelist  = nil
1076                    kernel   = nil
1077                end
1078            end
1079            if postlist then
1080                wrapup(d,index,#d,prelist,kernel,postlist)
1081            end
1082        end
1083    end
1084
1085    -- we can move more checks here
1086
1087    local function flatten(di)
1088        local r = di.__p__
1089        while r do
1090            local d = r.data
1091            local n = #d
1092            if d and n > 1 then
1093                n = checked(d,n)
1094            end
1095            local tg = r.tg
1096            if n == 1 and (tg == "mtext" or tg == "mrow") then
1097                r.skip = "comment" -- weird error
1098                r = r.__p__
1099            else
1100                break
1101            end
1102        end
1103    end
1104
1105    function checks.mtable(di)
1106        flatten(di)
1107        local d = di.data
1108-- -- left / right
1109-- -- local main   = { }
1110-- -- local left   = { }
1111-- local max    = 0
1112-- --
1113-- for i=1,#d do
1114--     local d = d[i]
1115--     if d.tg == "mtr" then
1116--         if #d.data > max then
1117--             max = #d.data
1118--         end
1119--     end
1120-- end
1121-- for i=1,#d do
1122--     local d = d[i]
1123--     if d.tg == "mtr" then
1124--         if #d.data < max then
1125--             local first = d.data[1]
1126--             specifications[first.fulltag].cols = max
1127--             first.attributes = { columnspan = max }
1128--         end
1129--     end
1130-- end
1131
1132-- if next(left) then
1133--     max = max + 1
1134--     for i=1,#main do
1135--         insert(main[i].data,1,left[i] or {
1136--             tg      = "mtd",
1137--             element = "mtd",
1138--             nature  = "mixed",
1139--             data    = { },
1140--         })
1141--     end
1142-- end
1143--     for i=1,#main do
1144-- print(#main[i].data,max)
1145--         if #main[i].data < max then
1146--             insert(main[i].data,{
1147--                 tg      = "mtd",
1148--                 element = "mtd",
1149--                 nature  = "mixed",
1150--                 data    = { },
1151--             })
1152--         end
1153--     end
1154-- -- if next(right) then
1155-- --     for i=1,#main do
1156-- --         insert(main[i].data,right[i] or {
1157-- --             tg      = "mtd",
1158-- --             element = "mtd",
1159-- --             nature  = "mixed",
1160-- --             data    = { },
1161-- --         })
1162-- --     end
1163-- -- end
1164-- di.data = main
1165         for i=1,#d do
1166             local d = d[i]
1167             if d.tg == "mtr" then
1168                 local d = d.data
1169                 for i=1,#d do
1170                     local d = d[i]
1171                     if d.tg == "mtd" then
1172                         -- okay
1173                     elseif d.content then
1174                         d.content = ""
1175                     else
1176                         d.skip = "comment" -- weird error
1177                     end
1178                 end
1179             elseif d.content then
1180                 d.content = ""
1181             else
1182                 d.skip = "comment" -- weird error
1183             end
1184         end
1185    end
1186
1187    function extras.mmultiscripts(di,element,n,fulltag)
1188        if not di.iscontinuation then
1189            local d      = di.data
1190            local sup    = found(d,"sup")
1191            local sub    = found(d,"sub")
1192            local presup = found(d,"presup")
1193            local presub = found(d,"presub")
1194            local prime  = found(d,"prime")
1195            local n = 1
1196            if sup or sub then
1197                n = n + 1 ; d[n] = sub or dummy_nucleus
1198                n = n + 1 ; d[n] = sup or dummy_nucleus
1199            end
1200            if prime then
1201                n = n + 1 ; d[n] = dummy_nucleus
1202                n = n + 1 ; d[n] = prime
1203            end
1204            if presup or presub then
1205                n = n + 1 ; d[n] = mprescripts
1206                n = n + 1 ; d[n] = presub or dummy_nucleus
1207                n = n + 1 ; d[n] = presup or dummy_nucleus
1208            end
1209            d.__i__ = n
1210        end
1211    end
1212
1213    function extras.msub(di,element,n,fulltag)
1214        local data = di.data
1215        local nuc = found(data)
1216        local sub = found(data,"sub")
1217        data[1] = nuc or dummy_nucleus
1218        data[2] = sub or dummy_nucleus
1219        data.__i__ = 2
1220        local sp = specifications[di.fulltag]
1221        if attributes then
1222            attributes.mathsubindex = sp.subindexed
1223        else
1224            di.attributes = {
1225                mathsubindex = sp.subindexed,
1226            }
1227        end
1228    end
1229
1230    function extras.msup(di,element,n,fulltag)
1231        local data = di.data
1232        local nuc = found(data)
1233        local sup = found(data,"sup")
1234or found(data,"prime")
1235        data[1] = nuc or dummy_nucleus
1236        data[2] = sup or dummy_nucleus
1237        data.__i__ = 2
1238        local sp = specifications[di.fulltag]
1239        local attributes = di.attributes
1240        if attributes then
1241            attributes.mathsupindex = sp.supindexed
1242        else
1243            di.attributes = {
1244                mathsupindex = sp.supindexed,
1245            }
1246        end
1247    end
1248
1249    function extras.msubsup(di,element,n,fulltag)
1250        local data = di.data
1251        local nuc = found(data)
1252        local sup = found(data,"sup")
1253or found(data,"prime")
1254        local sub = found(data,"sub")
1255        data[1] = nuc or dummy_nucleus
1256        data[2] = sub or dummy_nucleus
1257        data[3] = sup or dummy_nucleus
1258        data.__i__ = 3
1259        local sp = specifications[di.fulltag]
1260        if attributes then
1261            attributes.mathsupindex = sp.supindexed
1262            attributes.mathsubindex = sp.subindexed
1263        else
1264            di.attributes = {
1265                mathsupindex = sp.supindexed,
1266                mathsubindex = sp.subindexed,
1267            }
1268        end
1269    end
1270
1271    function extras.munder(di,element,n,fulltag)
1272        if di.tg == "msub" then
1273            extras.msub(di,element,n,fulltag)
1274        end
1275    end
1276
1277    function extras.mover(di,element,n,fulltag)
1278        if di.tg == "msup" then
1279            extras.msup(di,element,n,fulltag)
1280        end
1281    end
1282
1283    function extras.munderover(di,element,n,fulltag)
1284        if di.tg == "msubsup" then
1285            extras.msubsup(di,element,n,fulltag)
1286        end
1287    end
1288
1289end
1290