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