x-mathml.lmt /size: 32 Kb    last modification: 2025-02-21 11:03
1if not modules then modules = { } end modules ['x-mathml'] = {
2    version   = 1.001,
3    comment   = "companion to x-mathml.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
9-- This needs an upgrade to the latest greatest mechanisms. But ... it
10-- probably doesn't pay back as no mathml support ever did.
11
12local type, next = type, next
13local formatters, lower, find, gsub, match = string.formatters, string.lower, string.find, string.gsub, string.match
14local strip = string.strip
15local xmlsprint, xmlcprint, xmltext, xmlcontent, xmlempty = xml.sprint, xml.cprint, xml.text, xml.content, xml.empty
16local lxmlcollected, lxmlfilter = lxml.collected, lxml.filter
17local getid = lxml.getid
18local utfchar, utfcharacters, utfsplit, utflen = utf.char, utf.characters, utf.split, utf.len
19local lpegmatch, lpegpatterns = lpeg.match, lpeg.patterns
20local P, Cs = lpeg.P, lpeg.Cs
21
22local mathml      = { }
23moduledata.mathml = mathml
24lxml.mathml       = mathml -- for the moment
25
26local context = context
27
28local ctx_enabledelimiter      = context.enabledelimiter
29local ctx_disabledelimiter     = context.disabledelimiter
30local ctx_xmlflush             = context.xmlflush -- better xmlsprint
31
32local ctx_halign               = context.halign
33local ctx_noalign              = context.noalign
34local ctx_bgroup               = context.bgroup
35local ctx_egroup               = context.egroup
36local ctx_crcr                 = context.crcr
37
38local ctx_bTABLE               = context.bTABLE
39local ctx_eTABLE               = context.eTABLE
40local ctx_bTR                  = context.bTR
41local ctx_eTR                  = context.eTR
42local ctx_bTD                  = context.bTD
43local ctx_eTD                  = context.eTD
44
45local ctx_mn                   = context.mn
46local ctx_mi                   = context.mi
47local ctx_mo                   = context.mo
48local ctx_startimath           = context.startimath
49local ctx_ignorespaces         = context.ignorespaces
50local ctx_removeunwantedspaces = context.removeunwantedspaces
51local ctx_stopimath            = context.stopimath
52
53local ctx_mmlapplycsymbol      = context.mmlapplycsymbol
54
55local ctx_mathopnolimits       = context.mathopnolimits
56local ctx_left                 = context.left
57local ctx_right                = context.right
58
59-- an alternative is to remap to private codes, where we can have
60-- different properties .. to be done; this will move and become
61-- generic; we can then make the private ones active in math mode
62
63-- todo: handle opening/closing mo's here ... presentation mml is such a mess ...
64
65-- characters.registerentities()
66
67local doublebar = utfchar(0x2016)
68
69local n_replacements = {
70--  [" "]              = utfchar(0x2002),  -- "&textspace;" -> tricky, no &; in mkiv
71    ["."]              = "{.}",
72    [","]              = "{,}",
73    [" "]              = "",
74}
75
76local l_replacements = { -- in main table
77    ["|"]              = "\\mmlleftdelimiter\\vert",
78    ["{"]              = "\\mmlleftdelimiter\\lbrace",
79    ["("]              = "\\mmlleftdelimiter(",
80    ["["]              = "\\mmlleftdelimiter[",
81    ["<"]              = "\\mmlleftdelimiter<",
82    [doublebar]        = "\\mmlleftdelimiter\\Vert",
83}
84local r_replacements = { -- in main table
85    ["|"]              = "\\mmlrightdelimiter\\vert",
86    ["}"]              = "\\mmlrightdelimiter\\rbrace",
87    [")"]              = "\\mmlrightdelimiter)",
88    ["]"]              = "\\mmlrightdelimiter]",
89    [">"]              = "\\mmlrightdelimiter>",
90    [doublebar]        = "\\mmlrightdelimiter\\Vert",
91}
92
93-- todo: play with asciimode and avoid mmlchar
94
95-- we can use the proper names now! todo
96
97local o_replacements = { -- in main table
98    ["@l"]             = "\\mmlleftdelimiter.",
99    ["@r"]             = "\\mmlrightdelimiter.",
100    ["{"]              = "\\mmlleftdelimiter \\lbrace",
101    ["}"]              = "\\mmlrightdelimiter\\rbrace",
102    ["|"]              = "\\mmlleftorrightdelimiter\\vert",
103 -- ["."]              = "\\mmlleftorrightdelimiter.",
104    ["/"]              = "\\mmlleftorrightdelimiter\\solidus",
105    [doublebar]        = "\\mmlleftorrightdelimiter\\Vert",
106    ["("]              = "\\mmlleftdelimiter(",
107    [")"]              = "\\mmlrightdelimiter)",
108    ["["]              = "\\mmlleftdelimiter[",
109    ["]"]              = "\\mmlrightdelimiter]",
110 -- ["<"]              = "\\mmlleftdelimiter<",
111 -- [">"]              = "\\mmlrightdelimiter>",
112    ["#"]              = "\\mmlchar{35}",
113    ["$"]              = "\\mmlchar{36}", -- $
114    ["%"]              = "\\mmlchar{37}",
115    ["&"]              = "\\mmlchar{38}",
116    ["^"]              = "\\mmlchar{94}{}", -- strange, sometimes luatex math sees the char instead of \char so we
117    ["_"]              = "\\mmlchar{95}{}", -- need the {} ... has to do with active mess feedback into scanner
118    ["~"]              = "\\mmlchar{126}",
119    [" "]              = "",
120    ["°"]              = "^\\circ", -- hack
121
122    -- todo: take these and more from char-def
123
124    [""] = "\\int",
125    [""] = "\\oint",
126    [""] = "\\prod",
127    [""] = "\\sum",
128
129
130 -- [utfchar(0xF103C)] = "\\mmlleftdelimiter<",
131    [utfchar(0xF1026)] = "\\mmlchar{38}",
132    [utfchar(0x02061)]  = "", -- function applicator sometimes shows up in font
133 -- [utfchar(0xF103E)] = "\\mmlleftdelimiter>",
134 -- [utfchar(0x000AF)] = '\\mmlchar{"203E}', -- 0x203E
135}
136
137local simpleoperatorremapper = utf.remapper(o_replacements)
138
139--~ languages.data.labels.functions
140
141local i_replacements = {
142    ["sin"]         = "\\sin",
143    ["cos"]         = "\\cos",
144    ["abs"]         = "\\abs",
145    ["arg"]         = "\\arg",
146    ["codomain"]    = "\\codomain",
147    ["curl"]        = "\\curl",
148    ["determinant"] = "\\det",
149    ["divergence"]  = "\\div",
150    ["domain"]      = "\\domain",
151    ["gcd"]         = "\\gcd",
152    ["grad"]        = "\\grad",
153    ["identity"]    = "\\id",
154    ["image"]       = "\\image",
155    ["lcm"]         = "\\lcm",
156    ["lim"]         = "\\lim",
157    ["max"]         = "\\max",
158    ["median"]      = "\\median",
159    ["min"]         = "\\min",
160    ["mode"]        = "\\mode",
161    ["mod"]         = "\\mod",
162    ["polar"]       = "\\Polar",
163    ["exp"]         = "\\exp",
164    ["ln"]          = "\\ln",
165    ["log"]         = "\\log",
166    ["sin"]         = "\\sin",
167    ["arcsin"]      = "\\arcsin",
168    ["sinh"]        = "\\sinh",
169    ["arcsinh"]     = "\\arcsinh",
170    ["cos"]         = "\\cos",
171    ["arccos"]      = "\\arccos",
172    ["cosh"]        = "\\cosh",
173    ["arccosh"]     = "\\arccosh",
174    ["tan"]         = "\\tan",
175    ["arctan"]      = "\\arctan",
176    ["tanh"]        = "\\tanh",
177    ["arctanh"]     = "\\arctanh",
178    ["cot"]         = "\\cot",
179    ["arccot"]      = "\\arccot",
180    ["coth"]        = "\\coth",
181    ["arccoth"]     = "\\arccoth",
182    ["csc"]         = "\\csc",
183    ["arccsc"]      = "\\arccsc",
184    ["csch"]        = "\\csch",
185    ["arccsch"]     = "\\arccsch",
186    ["sec"]         = "\\sec",
187    ["arcsec"]      = "\\arcsec",
188    ["sech"]        = "\\sech",
189    ["arcsech"]     = "\\arcsech",
190    [" "]           = "",
191
192    ["false"]       = "{\\mathrm false}",
193    ["notanumber"]  = "{\\mathrm NaN}",
194    ["otherwise"]   = "{\\mathrm otherwise}",
195    ["true"]        = "{\\mathrm true}",
196    ["declare"]     = "{\\mathrm declare}",
197    ["as"]          = "{\\mathrm as}",
198
199}
200
201-- we could use a metatable or when accessing fallback on the
202-- key but at least we now have an overview
203
204local csymbols = {
205    arith1 = {
206        lcm                 = "lcm",
207        big_lcm             = "lcm",
208        gcd                 = "gcd",
209        big_gcd             = "big_gcd",
210        plus                = "plus",
211        unary_minus         = "minus",
212        minus               = "minus",
213        times               = "times",
214        divide              = "divide",
215        power               = "power",
216        abs                 = "abs",
217        root                = "root",
218        sum                 = "sum",
219        product             = "product",
220    },
221    fns = {
222        domain              = "domain",
223        range               = "codomain",
224        image               = "image",
225        identity            = "ident",
226     -- left_inverse        = "",
227     -- right_inverse       = "",
228        inverse             = "inverse",
229        left_compose        = "compose",
230        lambda              = "labmda",
231    },
232    linalg1 = {
233        vectorproduct       = "vectorproduct",
234        scalarproduct       = "scalarproduct",
235        outerproduct        = "outerproduct",
236        transpose           = "transpose",
237        determinant         = "determinant",
238        vector_selector     = "selector",
239     -- matrix_selector     = "matrix_selector",
240    },
241    logic1 = {
242        equivalent          = "equivalent",
243        ["not"]             = "not",
244        ["and"]             = "and",
245     -- big_and             = "",
246        ["xor"]             = "xor",
247     -- big_xor             = "",
248        ["or"]              = "or",
249     -- big-or              =  "",
250        implies             = "implies",
251        ["true"]            = "true",
252        ["false"]           = "false",
253    },
254    nums1 = {
255     -- based_integer       = "based_integer"
256        rational            = "rational",
257        inifinity           = "infinity",
258        e                   = "expenonentiale",
259        i                   = "imaginaryi",
260        pi                  = "pi",
261        gamma               = "gamma",
262        NaN                 = "NaN",
263    },
264    relation1 = {
265        eq                  = "eq",
266        lt                  = "lt",
267        gt                  = "gt",
268        neq                 = "neq",
269        leq                 = "leq",
270        geq                 = "geq",
271        approx              = "approx",
272    },
273    set1 = {
274        cartesian_product   = "cartesianproduct",
275        empty_set           = "emptyset",
276        map                 = "map",
277        size                = "card",
278    -- suchthat             = "suchthat",
279        set                 = "set",
280        intersect           = "intersect",
281    -- big_intersect        = "",
282        union               = "union",
283    -- big_union            = "",
284        setdiff             = "setdiff",
285        subset              = "subset",
286        ["in"]              = "in",
287        notin               = "notin",
288        prsubset            = "prsubset",
289        notsubset           = "notsubset",
290        notprsubset         = "notprsubset",
291    },
292    veccalc1 = {
293        divergence          = "divergence",
294        grad                = "grad",
295        curl                = "curl",
296        laplacian           = "laplacian",
297        Laplacian           = "laplacian",
298    },
299    calculus1 = {
300        diff                = "diff",
301     -- nthdiff             = "",
302        partialdiff         = "partialdiff",
303        int                 = "int",
304     -- defint              = "defint",
305    },
306    integer1 = {
307        factorof            = "factorof",
308        factorial           = "factorial",
309        quotient            = "quotient",
310        remainder           = "rem",
311    },
312    linalg2 = {
313        vector              = "vector",
314        matrix              = "matrix",
315        matrixrow           = "matrixrow",
316    },
317    mathmkeys = {
318     -- equiv               = "",
319     -- contentequiv        =  "",
320     -- contentequiv_strict = "",
321    },
322    rounding1 = {
323        ceiling             = "ceiling",
324        floor               = "floor",
325     -- trunc               = "trunc",
326     -- round               = "round",
327    },
328    setname1 = {
329        P                   = "primes",
330        N                   = "naturalnumbers",
331        Z                   = "integers",
332        rationals           = "rationals",
333        R                   = "reals",
334        complexes           = "complexes",
335    },
336    complex1 = {
337     -- complex_cartesian   = "complex_cartesian", -- ci ?
338        real                = "real",
339        imaginary           = "imaginary",
340     -- complex_polar       = "complex_polar", -- ci ?
341        argument            = "arg",
342        conjugate           = "conjugate",
343    },
344    interval1 = { -- not an apply
345     -- integer_interval    = "integer_interval",
346        interval            = "interval",
347        interval_oo         = { tag = "interval", closure = "open" },
348        interval_cc         = { tag = "interval", closure = "closed" },
349        interval_oc         = { tag = "interval", closure = "open-closed" },
350        interval_co         = { tag = "interval", closure = "closed-open" },
351    },
352    linalg3 = {
353     -- vector              = "vector.column",
354     -- matrixcolumn        = "matrixcolumn",
355     -- matrix              = "matrix.column",
356    },
357    minmax1 = {
358        min                 = "min",
359     -- big_min             = "",
360        max                 = "max",
361     -- big_max             = "",
362    },
363    piece1 = {
364        piecewise           = "piecewise",
365        piece               = "piece",
366        otherwise           = "otherwise",
367    },
368    error1 = {
369     -- unhandled_symbol    = "",
370     -- unexpected_symbol   = "",
371     -- unsupported_CD      = "",
372    },
373    limit1 = {
374     -- limit               = "limit",
375     -- both_sides          = "both_sides",
376     -- above               = "above",
377     -- below               = "below",
378     -- null                = "null",
379        tendsto             = "tendsto",
380    },
381    list1 = {
382     -- map                 = "",
383     -- suchthat            = "",
384     -- list                = "list",
385    },
386    multiset1 = {
387        size                = { tag = "card",             type = "multiset" },
388        cartesian_product   = { tag = "cartesianproduct", type = "multiset" },
389        empty_set           = { tag = "emptyset",         type = "multiset" },
390     -- multi_set           = { tag = "multiset",         type = "multiset" },
391        intersect           = { tag = "intersect",        type = "multiset" },
392     -- big_intersect       = "",
393        union               = { tag = "union",            type = "multiset" },
394     -- big_union           = "",
395        setdiff             = { tag = "setdiff",          type = "multiset" },
396        subset              = { tag = "subset",           type = "multiset" },
397        ["in"]              = { tag = "in",               type = "multiset" },
398        notin               = { tag = "notin",            type = "multiset" },
399        prsubset            = { tag = "prsubset",         type = "multiset" },
400        notsubset           = { tag = "notsubset",        type = "multiset" },
401        notprsubset         = { tag = "notprsubset",      type = "multiset" },
402    },
403    quant1 = {
404        forall              = "forall",
405        exists              = "exists",
406    },
407    s_dist = {
408     -- mean                = "mean.dist",
409     -- sdev                = "sdev.dist",
410     -- variance            = "variance.dist",
411     -- moment              = "moment.dist",
412    },
413    s_data = {
414        mean                = "mean",
415        sdev                = "sdev",
416        variance            = "vriance",
417        mode                = "mode",
418        median              = "median",
419        moment              = "moment",
420    },
421    transc1 = {
422        log                 = "log",
423        ln                  = "ln",
424        exp                 = "exp",
425        sin                 = "sin",
426        cos                 = "cos",
427        tan                 = "tan",
428        sec                 = "sec",
429        csc                 = "csc",
430        cot                 = "cot",
431        sinh                = "sinh",
432        cosh                = "cosh",
433        tanh                = "tanh",
434        sech                = "sech",
435        csch                = "cscs",
436        coth                = "coth",
437        arcsin              = "arcsin",
438        arccos              = "arccos",
439        arctan              = "arctan",
440        arcsec              = "arcsec",
441        arcscs              = "arccsc",
442        arccot              = "arccot",
443        arcsinh             = "arcsinh",
444        arccosh             = "arccosh",
445        arctanh             = "arstanh",
446        arcsech             = "arcsech",
447        arccsch             = "arccsch",
448        arccoth             = "arccoth",
449    },
450}
451
452function xml.functions.remapmmlcsymbol(e)
453    local at = e.at
454    local cd = at.cd
455    if cd then
456        cd = csymbols[cd]
457        if cd then
458            local tx = e.dt[1]
459            if tx and tx ~= "" then
460                local tg = cd[tx]
461                if tg then
462                    at.cd = nil
463                    at.cdbase = nil
464                    e.dt = { }
465                    if type(tg) == "table" then
466                        for k, v in next, tg do
467                            if k == "tag" then
468                                e.tg = v
469                            else
470                                at[k] = v
471                            end
472                        end
473                    else
474                        e.tg = tg
475                    end
476                end
477            end
478        end
479    end
480end
481
482function xml.functions.remapmmlbind(e)
483    e.tg = "apply"
484end
485
486function xml.functions.remapopenmath(e)
487    local tg = e.tg
488    if tg == "OMOBJ" then
489        e.tg = "math"
490    elseif tg == "OMA" then
491        e.tg = "apply"
492    elseif tg == "OMB" then
493        e.tg = "apply"
494    elseif tg == "OMS" then
495        local at = e.at
496        e.tg = "csymbol"
497        e.dt = { at.name or "unknown" }
498        at.name = nil
499    elseif tg == "OMV" then
500        local at = e.at
501        e.tg = "ci"
502        e.dt = { at.name or "unknown" }
503        at.name = nil
504    elseif tg == "OMI" then
505        e.tg = "ci"
506    end
507    e.rn = "mml"
508end
509
510function mathml.checked_operator(str)
511    context(simpleoperatorremapper(str))
512end
513
514function mathml.stripped(str)
515    context(strip(str))
516end
517
518local p_entity  = (P("&") * ((1-P(";"))^0) * P(";"))
519local p_utfchar = lpegpatterns.utf8character
520local p_spacing = lpegpatterns.whitespace^1
521
522local p_mn      =  Cs((p_entity/"" + p_spacing/utfchar(0x205F) + p_utfchar/n_replacements)^0)
523local p_strip   =  Cs((p_entity/""                             + p_utfchar               )^0)
524local p_mi      =  Cs((p_entity/""                             + p_utfchar/i_replacements)^0)
525
526function mathml.mn(id)
527    -- maybe at some point we need to interpret the number, but
528    -- currently we assume an upright font
529    ctx_mn(lpegmatch(p_mn,xmlcontent(getid(id)) or ""))
530end
531
532function mathml.mo(id)
533    local str = lpegmatch(p_strip,xmlcontent(getid(id)) or "")
534    context(simpleoperatorremapper(str) or str)
535end
536
537interfaces.implement {
538    name      = "remapmathoperator",
539    public    = true,
540    protected = true,
541    arguments = "string",
542    actions   = function(str)
543        context(simpleoperatorremapper(str) or str)
544    end
545}
546
547function mathml.mi(id)
548    -- we need to strip comments etc .. todo when reading in tree
549    local e = getid(id)
550    local str = e.dt
551    if type(str) == "table" then
552        local n = #str
553        if n == 0 then
554            -- nothing to do
555        elseif n == 1 then
556            local first = str[1]
557            if type(first) == "string" then
558                -- local str = gsub(first,"&.-;","") -- bah
559                -- local rep = i_replacements[str]
560                -- if not rep then
561                --     rep = gsub(str,".",i_replacements)
562                -- end
563                local str = lpegmatch(p_strip,first)
564                local rep = i_replacements[str] or lpegmatch(p_mi,str)
565                context(rep)
566             -- ctx_mi(rep)
567            else
568                ctx_xmlflush(id) -- xmlsprint or so
569            end
570        else
571            ctx_xmlflush(id) -- xmlsprint or so
572        end
573    else
574        ctx_xmlflush(id) -- xmlsprint or so
575    end
576end
577
578function mathml.mfenced(id) -- multiple separators
579    id = getid(id)
580    local at         = id.at
581    local left       = at.open       or "("
582    local right      = at.close      or ")"
583    local separators = at.separators or ","
584    local l          = l_replacements[left]
585    local r          = r_replacements[right]
586    ctx_enabledelimiter()
587    if l then
588        context(l_replacements[left] or o_replacements[left] or "")
589    else
590        context(o_replacements["@l"])
591        context(left)
592    end
593    ctx_disabledelimiter()
594    local collected = lxmlfilter(id,"/*") -- check the *
595    if collected then
596        local n = #collected
597        if n == 0 then
598            -- skip
599        elseif n == 1 then
600            xmlsprint(collected[1]) -- to be checked
601        else
602            local t = utfsplit(separators,true)
603            for i=1,n do
604                xmlsprint(collected[i]) -- to be checked
605                if i < n then
606                    local m = t[i] or t[#t] or ""
607                    if m == "|" then
608                        m = "\\enabledelimiter\\middle|\\relax\\disabledelimiter"
609                    elseif m == doublebar then
610                        m = "\\enabledelimiter\\middle|\\relax\\disabledelimiter"
611                    elseif m == "{" then
612                        m = "\\{"
613                    elseif m == "}" then
614                        m = "\\}"
615                    end
616                    context(m)
617                end
618            end
619        end
620    end
621    ctx_enabledelimiter()
622    if r then
623        context(r_replacements[right] or o_replacements[right] or "")
624    else
625        context(right)
626        context(o_replacements["@r"])
627    end
628    ctx_disabledelimiter()
629end
630
631-- local function flush(e,tag,toggle)
632--     if tag == "none" then
633--      -- if not toggle then
634--         context("{}") -- {} starts a new ^_ set
635--      -- end
636--     elseif toggle then
637--         context("^{")
638--         xmlsprint(e.dt)
639--         context("}{}") -- {} starts a new ^_ set
640--     else
641--         context("_{")
642--         xmlsprint(e.dt)
643--         context("}")
644--     end
645--     return not toggle
646-- end
647--
648-- function mathml.mmultiscripts(id)
649--     local done, toggle = false, false
650--     for e in lxmlcollected(id,"/*") do
651--         local tag = e.tg
652--         if tag == "mprescripts" then
653--             context("{}")
654--             done = true
655--         elseif done then
656--             toggle = flush(e,tag,toggle)
657--         end
658--     end
659--     local done, toggle = false, false
660--     for e in lxmlcollected(id,"/*") do
661--         local tag = e.tg
662--         if tag == "mprescripts" then
663--             break
664--         elseif done then
665--             toggle = flush(e,tag,toggle)
666--         else
667--             xmlsprint(e)
668--             done = true
669--         end
670--     end
671-- end
672
673function mathml.mmultiscripts(id)
674    local prescripts = false
675    local subscripts = true
676    local firstdone  = false
677    for e in lxmlcollected(id,"/*") do
678        if firstdone then
679            xmlsprint(e.dt)
680            firstdone = true
681        else
682            local tag = e.tg
683            if tag == "none" then
684                subscripts = not subscripts
685            elseif tag == "prescripts" then
686                prescripts = true
687                subscripts = true
688            elseif subscripts then
689                if prescripts then
690                    context("\\subprescript{")
691                    xmlsprint(e.dt)
692                    context("}")
693                end
694                subscripts = false
695            else
696                if prescripts then
697                    context("\\superprescript{")
698                    xmlsprint(e.dt)
699                    context("}")
700                end
701                subscripts = true
702            end
703        end
704    end
705    local prescripts = false
706    local subscripts = true
707    local firstdone  = false
708    for e in lxmlcollected(id,"/*") do
709        if firstdone then
710            firstdone = true
711        else
712            local tag = e.tg
713            if tag == "none" then
714                subscripts = not subscripts
715            elseif tag == "prescripts" then
716                prescripts = true
717                subscripts = true
718            elseif subscripts then
719                if not prescripts then
720                    context("\\subscript{")
721                    xmlsprint(e.dt)
722                    context("}")
723                end
724                subscripts = false
725            else
726                if not prescripts then
727                    context("\\superscript{")
728                    xmlsprint(e.dt)
729                    context("}")
730                end
731                subscripts = true
732            end
733        end
734    end
735end
736
737local columnalignments = {
738    left   = "flushleft",
739    right  = "flushright",
740    center = "middle",
741}
742
743local rowalignments = {
744    top      = "high",
745    bottom   = "low",
746    center   = "lohi",
747    baseline = "top",
748    axis     = "lohi",
749}
750
751local frametypes = {
752    none   = "off",
753    solid  = "on",
754    dashed = "on",
755}
756
757-- crazy element ... should be a proper structure instead of such a mess
758
759function mathml.mcolumn(root)
760    root = getid(root)
761    local matrix, numbers = { }, 0
762    local function collect(m,e)
763        local tag = e.tg
764        if tag == "mi" or tag == "mn" or tag == "mo" or tag == "mtext" then
765            local str = xmltext(e)
766            str = lpegmatch(p_strip,str)
767            for s in utfcharacters(str) do
768                m[#m+1] = { tag, s }
769            end
770            if tag == "mn" then
771                local n = utflen(str)
772                if n > numbers then
773                    numbers = n
774                end
775            end
776        elseif tag == "mspace" or tag == "mline" then
777            local str = e.at.spacing or ""
778            for s in utfcharacters(str) do
779                m[#m+1] = { tag, s }
780            end
781     -- elseif tag == "mline" then
782     --     m[#m+1] = { tag, e }
783        end
784    end
785    for e in lxmlcollected(root,"/*") do
786        local m = { }
787        matrix[#matrix+1] = m
788        if e.tg == "mrow" then
789            -- only one level
790            for e in lxmlcollected(e,"/*") do
791                collect(m,e)
792            end
793        else
794            collect(m,e)
795        end
796    end
797    ctx_halign()
798    ctx_bgroup()
799    context([[\hss\startimath\alignmark\stopimath\aligntab\startimath\alignmark\stopimath\cr]])
800    for i=1,#matrix do
801        local m = matrix[i]
802        local mline = true
803        for j=1,#m do
804            if m[j][1] ~= "mline" then
805                mline = false
806                break
807            end
808        end
809        if mline then
810            ctx_noalign([[\obeydepth\nointerlineskip]])
811        end
812        for j=1,#m do
813            local mm = m[j]
814            local tag, chr = mm[1], mm[2]
815            if tag == "mline" then
816                -- This code is under construction ... I need some real motivation
817                -- to deal with this kind of crap.
818--~                 local n, p = true, true
819--~                 for c=1,#matrix do
820--~                     local mc = matrix[c][j]
821--~                     if mc then
822--~                         mc = mc[2]
823--~                         if type(mc) ~= "string" then
824--~                             n, p = false, false
825--~                             break
826--~                         elseif find(mc,"^[%d ]$") then -- rangecheck is faster
827--~                             -- digit
828--~                         elseif not find(mc,"^[%.%,]$") then -- rangecheck is faster
829--~                             -- punctuation
830--~                         else
831--~                             n = false
832--~                             break
833--~                         end
834--~                     end
835--~                 end
836--~                 if n then
837--~                     chr = "\\mmlmcolumndigitrule"
838--~                 elseif p then
839--~                     chr = "\\mmlmcolumnpunctuationrule"
840--~                 else
841--~                     chr = "\\mmlmcolumnsymbolrule" -- should be widest char
842--~                 end
843                chr = "\\hrulefill"
844            elseif tag == "mspace" then
845                chr = "\\mmlmcolumndigitspace" -- utfchar(0x2007)
846            end
847            if j == numbers + 1 then
848                context("\\aligntab")
849            end
850            local nchr = n_replacements[chr]
851            context(nchr or chr)
852        end
853        ctx_crcr()
854    end
855    ctx_egroup()
856end
857
858local spacesplitter = lpeg.tsplitat(" ")
859
860function mathml.mtable(root)
861    -- todo: align, rowspacing, columnspacing, rowlines, columnlines
862    root = getid(root)
863    local at           = root.at
864    local rowalign     = at.rowalign
865    local columnalign  = at.columnalign
866    local frame        = at.frame
867    local rowaligns    = rowalign    and lpegmatch(spacesplitter,rowalign)
868    local columnaligns = columnalign and lpegmatch(spacesplitter,columnalign)
869    local frames       = frame       and lpegmatch(spacesplitter,frame)
870    local framespacing = at.framespacing or "0pt"
871    local framespacing = at.framespacing or "-\\ruledlinewidth" -- make this an option
872
873    ctx_bTABLE { frame = frametypes[frame or "none"] or "off", offset = framespacing, background = "" } -- todo: use xtables and definextable
874    for e in lxmlcollected(root,"/(mml:mtr|mml:mlabeledtr)") do
875        ctx_bTR()
876        local at = e.at
877        local col = 0
878        local rfr = at.frame       or (frames       and frames      [#frames])
879        local rra = at.rowalign    or (rowaligns    and rowaligns   [#rowaligns])
880        local rca = at.columnalign or (columnaligns and columnaligns[#columnaligns])
881        local ignorelabel = e.tg == "mlabeledtr"
882        for e in lxmlcollected(e,"/mml:mtd") do -- nested we can use xml.collected
883            col = col + 1
884            if ignorelabel and col == 1 then
885                -- get rid of label, should happen at the document level
886            else
887                local at = e.at
888                local rowspan    = at.rowspan    or 1
889                local columnspan = at.columnspan or 1
890                local cra = rowalignments   [at.rowalign    or (rowaligns    and rowaligns   [col]) or rra or "center"] or "lohi"
891                local cca = columnalignments[at.columnalign or (columnaligns and columnaligns[col]) or rca or "center"] or "middle"
892                local cfr = frametypes      [at.frame       or (frames       and frames      [col]) or rfr or "none"  ] or "off"
893                ctx_bTD { align = formatters["{%s,%s}"](cra,cca), frame = cfr, nx = columnspan, ny = rowspan }
894                if xmlempty(e,".") then
895                    -- nothing, else hsize max
896                else
897                    ctx_startimath()
898                 -- ctx_ignorespaces()
899                    xmlcprint(e)
900                 -- ctx_removeunwantedspaces()
901                    ctx_stopimath()
902                end
903                ctx_eTD()
904            end
905        end
906     -- if e.tg == "mlabeledtr" then
907     --     ctx_bTD()
908     --     xmlcprint(xml.first(e,"/!mml:mtd"))
909     --     ctx_eTD()
910     -- end
911        ctx_eTR()
912    end
913    ctx_eTABLE()
914end
915
916function mathml.csymbol(root)
917    root = getid(root)
918    local at = root.at
919    local encoding = at.encoding or ""
920    local hash = url.hashed(lower(at.definitionUrl or ""))
921    local full = hash.original or ""
922    local base = hash.path or ""
923    local text = strip(xmltext(root) or "")
924    ctx_mmlapplycsymbol(full,base,encoding,text)
925end
926
927local p = lpeg.Cs(((1-lpegpatterns.whitespace)^1 / "mml:enclose:%0"  + (lpegpatterns.whitespace^1)/",")^1)
928
929function mathml.menclosepattern(root)
930    root = getid(root)
931    local a = root.at.notation
932    if a and a ~= "" then
933        context(lpegmatch(p,a))
934    end
935end
936
937function xml.is_element(e,name)
938    return type(e) == "table" and (not name or e.tg == name)
939end
940
941function mathml.cpolar(root)
942    root = getid(root)
943    local dt = root.dt
944    ctx_mathopnolimits("Polar")
945    ctx_left(false,"(")
946    for k=1,#dt do
947        local dk = dt[k]
948        if xml.is_element(dk,"sep") then
949            context(",")
950        else
951            xmlsprint(dk)
952        end
953    end
954    ctx_right(false,")")
955end
956
957-- crap .. maybe in char-def a mathml overload
958
959local mathmleq = {
960    [utfchar(0x00AF)] = utfchar(0x203E),
961}
962
963function mathml.extensible(chr)
964    context(mathmleq[chr] or chr)
965end
966
967--
968
969local function install(name,action)
970    interfaces.implement {
971        name      = name,
972        public    = true,
973     -- protected = true, -- some definitely not !
974        arguments = "argument",
975        actions   = action,
976    }
977end
978
979install("mathml_mi",                mathml.mi)
980install("mathml_mo",                mathml.mo)
981install("mathml_mn",                mathml.mn)
982install("mathml_mfenced",           mathml.mfenced)
983install("mathml_mmultiscripts",     mathml.mmultiscripts)
984install("mathml_menclosepattern",   mathml.menclosepattern)
985install("mathml_mtable",            mathml.mtable)
986install("mathml_mcolumn",           mathml.mcolumn)
987install("mathml_extensible",        mathml.extensible)
988
989install("mathml_csymbol",           mathml.csymbol)
990install("mathml_cpolar",            mathml.cpolar)
991