if not modules then modules = { } end modules ['x-mathml'] = { version = 1.001, comment = "companion to x-mathml.tex", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files" } local utf = unicode.utf8 lxml = lxml or { } lxml.mml = lxml.mml or { } local texsprint = tex.sprint local format = string.format local lower = string.lower local utfchar = utf.char local utffind = utf.find local xmlsprint = xml.sprint local xmlcprint = xml.cprint local utfcharacters, utfvalues = string.utfcharacters, string.utfvalues -- an alternative is to remap to private codes, where we can have -- different properties .. to be done; this will move and become -- generic -- todo: handle opening/closing mo's here ... presentation mml is such a mess ... local doublebar = utfchar(0x2016) local n_replacements = { -- [" "] = utfchar(0x2002), -- "&textspace;" -> tricky, no &; in mkiv ["."] = "{.}", [","] = "{,}", [" "] = "", } local l_replacements = { -- in main table ["|"] = "\\mmlleftdelimiter\\vert", ["{"] = "\\mmlleftdelimiter\\lbrace", ["("] = "\\mmlleftdelimiter(", ["["] = "\\mmlleftdelimiter[", ["<"] = "\\mmlleftdelimiter<", [doublebar] = "\\mmlleftdelimiter\\Vert", } local r_replacements = { -- in main table ["|"] = "\\mmlrightdelimiter\\vert", ["}"] = "\\mmlrightdelimiter\\rbrace", [")"] = "\\mmlrightdelimiter)", ["]"] = "\\mmlrightdelimiter]", [">"] = "\\mmlrightdelimiter>", [doublebar] = "\\mmlrightdelimiter\\Vert", } local o_replacements = { -- in main table ["@l"] = "\\mmlleftdelimiter.", ["@r"] = "\\mmlrightdelimiter.", ["{"] = "\\mmlleftdelimiter\\lbrace", ["}"] = "\\mmlrightdelimiter\\rbrace", ["|"] = "\\mmlleftorrightdelimiter\\vert", [doublebar] = "\\mmlleftorrightdelimiter\\Vert", ["("] = "\\mmlleftdelimiter(", [")"] = "\\mmlrightdelimiter)", ["["] = "\\mmlleftdelimiter[", ["]"] = "\\mmlrightdelimiter]", ["<"] = "\\mmlleftdelimiter<", [">"] = "\\mmlrightdelimiter>", ["#"] = "\\mmlchar{35}", ["$"] = "\\mmlchar{36}", -- $ ["%"] = "\\mmlchar{37}", ["&"] = "\\mmlchar{38}", ["^"] = "\\mmlchar{94}{}", -- strange, sometimes luatex math sees the char instead of \char ["_"] = "\\mmlchar{95}{}", -- so we need the {} ["~"] = "\\mmlchar{126}", [" "] = "", ["°"] = "^\\circ", -- hack [utfchar(0xF103C)] = "\\mmlleftdelimiter<", [utfchar(0xF1026)] = "\\mmlchar{38}", [utfchar(0xF103E)] = "\\mmlleftdelimiter>", } local i_replacements = { ["sin"] = "\\mathopnolimits{sin}", ["cos"] = "\\mathopnolimits{cos}", ["abs"] = "\\mathopnolimits{abs}", ["arg"] = "\\mathopnolimits{arg}", ["codomain"] = "\\mathopnolimits{codomain}", ["curl"] = "\\mathopnolimits{curl}", ["determinant"] = "\\mathopnolimits{det}", ["divergence"] = "\\mathopnolimits{div}", ["domain"] = "\\mathopnolimits{domain}", ["gcd"] = "\\mathopnolimits{gcd}", ["grad"] = "\\mathopnolimits{grad}", ["identity"] = "\\mathopnolimits{id}", ["image"] = "\\mathopnolimits{image}", ["lcm"] = "\\mathopnolimits{lcm}", ["max"] = "\\mathopnolimits{max}", ["median"] = "\\mathopnolimits{median}", ["min"] = "\\mathopnolimits{min}", ["mode"] = "\\mathopnolimits{mode}", ["mod"] = "\\mathopnolimits{mod}", ["polar"] = "\\mathopnolimits{Polar}", ["exp"] = "\\mathopnolimits{exp}", ["ln"] = "\\mathopnolimits{ln}", ["log"] = "\\mathopnolimits{log}", ["sin"] = "\\mathopnolimits{sin}", ["arcsin"] = "\\mathopnolimits{arcsin}", ["sinh"] = "\\mathopnolimits{sinh}", ["arcsinh"] = "\\mathopnolimits{arcsinh}", ["cos"] = "\\mathopnolimits{cos}", ["arccos"] = "\\mathopnolimits{arccos}", ["cosh"] = "\\mathopnolimits{cosh}", ["arccosh"] = "\\mathopnolimits{arccosh}", ["tan"] = "\\mathopnolimits{tan}", ["arctan"] = "\\mathopnolimits{arctan}", ["tanh"] = "\\mathopnolimits{tanh}", ["arctanh"] = "\\mathopnolimits{arctanh}", ["cot"] = "\\mathopnolimits{cot}", ["arccot"] = "\\mathopnolimits{arccot}", ["coth"] = "\\mathopnolimits{coth}", ["arccoth"] = "\\mathopnolimits{arccoth}", ["csc"] = "\\mathopnolimits{csc}", ["arccsc"] = "\\mathopnolimits{arccsc}", ["csch"] = "\\mathopnolimits{csch}", ["arccsch"] = "\\mathopnolimits{arccsch}", ["sec"] = "\\mathopnolimits{sec}", ["arcsec"] = "\\mathopnolimits{arcsec}", ["sech"] = "\\mathopnolimits{sech}", ["arcsech"] = "\\mathopnolimits{arcsech}", [" "] = "", ["false"] = "{\\mr false}", ["notanumber"] = "{\\mr NaN}", ["otherwise"] = "{\\mr otherwise}", ["true"] = "{\\mr true}", ["declare"] = "{\\mr declare}", ["as"] = "{\\mr as}", } local csymbols = { arith1 = { lcm = "lcm", big_lcm = "lcm", gcd = "gcd", big_gcd = "big_gcd", plus = "plus", unary_minus = "minus", minus = "minus", times = "times", divide = "divide", power = "power", abs = "abs", root = "root", sum = "sum", product ="product", }, fns = { domain = "domain", range = "codomain", image = "image", identity = "ident", --~ left_inverse = "", --~ right_inverse = "", inverse = "inverse", left_compose = "compose", lambda = "labmda", }, linalg1 = { vectorproduct = "vectorproduct", scalarproduct = "scalarproduct", outerproduct = "outerproduct", transpose = "transpose", determinant = "determinant", vector_selector = "selector", --~ matrix_selector = "matrix_selector", }, logic1 = { equivalent = "equivalent", ["not"] = "not", ["and"] = "and", --~ big_and = "", ["xor"] = "xor", --~ big_xor = "", ["or"] = "or", --~ big-or= "", implies = "implies", ["true"] = "true", ["false"] = "false", }, nums1 = { --~ based_integer = "based_integer" rational = "rational", inifinity = "infinity", e = "expenonentiale", i ="imaginaryi", pi = "pi", gamma = "gamma", NaN, "NaN", }, relation1 = { eq = "eq", lt = "lt", gt = "gt", neq = "neq", leq = "leq", geq = "geq", approx = "approx", }, set1 = { cartesian_product = "cartesianproduct", empty_set = "emptyset", map = "map", size = "card", --~ suchthat = "suchthat", set = "set", intersect = "intersect", --~ big_intersect = "", union = "union", --~ big_union = "", setdiff = "setdiff", subset = "subset", ["in"] = "in", notin = "notin", prsubset = "prsubset", notsubset = "notsubset", notprsubset = "notprsubset", }, veccalc1 = { divergence = "divergence", grad = "grad", curl = "curl", Laplacian = "laplacian", }, calculus1 = { diff = "diff", --~ nthdiff = "", partialdiff = "partialdiff", int = "int", --~ defint = "defint", }, integer1 = { factorof = "factorof", factorial = "factorial", quotient = "quotient", remainder = "rem", }, linalg2 = { vector = "vector", matrix = "matrix", matrixrow = "matrixrow", }, mathmkeys = { --~ equiv = "", --~ contentequiv = "", --~ contentequiv_strict = "", }, rounding1 = { ceiling = "ceiling", floor = "floor", --~ trunc = "trunc", --~ round = "round", }, setname1 = { P = "primes", N = "naturalnumbers", Z = "integers", rationals = "rationals", R = "reals", complexes = "complexes", }, complex1 = { --~ complex_cartesian = "complex_cartesian", -- ci ? real = "real", imaginary = "imaginary", --~ complex_polar = "complex_polar", -- ci ? argument = "arg", conjugate = "conjugate", }, interval1 = { -- not an apply --~ "integer_interval" = "integer_interval", interval = "interval", interval_oo = { tag = "interval", closure = "open" }, interval_cc = { tag = "interval", closure = "closed" }, interval_oc = { tag = "interval", closure = "open-closed" }, interval_co = { tag = "interval", closure = "closed-open" }, }, linalg3 = { --~ vector = "vector.column", --~ matrixcolumn = "matrixcolumn", --~ matrix = "matrix.column", }, minmax1 = { min = "min", --~ big_min = "", max = "max", --~ big_max = "", }, piece1 = { piecewise = "piecewise", piece = "piece", otherwise = "otherwise", }, error1 = { --~ unhandled_symbol = "", --~ unexpected_symbol = "", --~ unsupported_CD = "", }, limit1 = { --~ limit = "limit", --~ both_sides = "both_sides", --~ above = "above", --~ below = "below", --~ null = "null", tendsto = "tendsto", }, list1 = { --~ map = "", --~ suchthat = "", --~ list = "list", }, multiset1 = { size = { tag = "card", type="multiset" }, cartesian_product = { tag = "cartesianproduct", type="multiset" }, empty_set = { tag = "emptyset", type="multiset" }, --~ multi_set = { tag = "multiset", type="multiset" }, intersect = { tag = "intersect", type="multiset" }, --~ big_intersect = "", union = { tag = "union", type="multiset" }, --~ big_union = "", setdiff = { tag = "setdiff", type="multiset" }, subset = { tag = "subset", type="multiset" }, ["in"] = { tag = "in", type="multiset" }, notin = { tag = "notin", type="multiset" }, prsubset = { tag = "prsubset", type="multiset" }, notsubset = { tag = "notsubset", type="multiset" }, notprsubset = { tag = "notprsubset", type="multiset" }, }, quant1 = { forall = "forall", exists = "exists", }, s_dist = { --~ mean = "mean.dist", --~ sdev = "sdev.dist", --~ variance = "variance.dist", --~ moment = "moment.dist", }, s_data = { mean = "mean", sdev = "sdev", variance = "vriance", mode = "mode", median = "median", moment = "moment", }, transc1 = { log = "log", ln = "ln", exp = "exp", sin = "sin", cos = "cos", tan = "tan", sec = "sec", csc = "csc", cot = "cot", sinh = "sinh", cosh = "cosh", tanh = "tanh", sech = "sech", csch = "cscs", coth = "coth", arcsin = "arcsin", arccos = "arccos", arctan = "arctan", arcsec = "arcsec", arcscs = "arccsc", arccot = "arccot", arcsinh = "arcsinh", arccosh = "arccosh", arctanh = "arstanh", arcsech = "arcsech", arccsch = "arccsch", arccoth = "arccoth", }, } function xml.functions.remapmmlcsymbol(r,d,k) local dk = d[k] local at = dk.at local cd = at.cd if cd then cd = csymbols[cd] if cd then local tx = dk.dt[1] if tx and tx ~= "" then local tg = cd[tx] if tg then at.cd = nil at.cdbase = nil dk.dt = { } if type(tg) == "table" then for k, v in pairs(tg) do if k == "tag" then dk.tg = v else at[k] = v end end else dk.tg = tg end end end end end end function xml.functions.remapmmlbind(r,d,k) d[k].tg = "apply" end function xml.functions.remapopenmath(r,d,k) local dk = d[k] local tg = dk.tg if tg == "OMOBJ" then dk.tg = "math" elseif tg == "OMA" then dk.tg = "apply" elseif tg == "OMB" then dk.tg = "apply" elseif tg == "OMS" then -- xml.functions.remapmmlcsymbol(r,d,k) local at = dk.at dk.tg = "csymbol" dk.dt = { at.name or "unknown" } at.name = nil elseif tg == "OMV" then local at = dk.at dk.tg = "ci" dk.dt = { at.name or "unknown" } at.name = nil elseif tg == "OMI" then dk.tg = "ci" end dk.rn = "mml" end function lxml.mml.checked_operator(str) texsprint(tex.ctxcatcodes,(utf.gsub(str,".",o_replacements))) end function lxml.mml.stripped(str) tex.sprint(tex.ctxcatcodes,str:strip()) end function lxml.mml.mn(id,pattern) -- maybe at some point we need to interpret the number, but -- currently we assume an upright font local str = xml.content(lxml.id(id),pattern) or "" -- str = str:gsub("^%s*(.-)%s*$","%1") str = str:gsub("(%s+)",utfchar(0x205F)) -- medspace e.g.: twenty one (nbsp is not seen) texsprint(tex.ctxcatcodes,(str:gsub(".",n_replacements))) end function characters.remapentity(chr,slot) texsprint(format("{\\catcode%s=13\\xdef%s{\\string%s}}",slot,utfchar(slot),chr)) end function lxml.mml.mo(id,pattern) local str = xml.content(lxml.id(id),pattern) or "" texsprint(tex.ctxcatcodes,(utf.gsub(str,".",o_replacements))) end function lxml.mml.mi(id,pattern) local str = xml.content(lxml.id(id),pattern) or "" -- str = str:gsub("^%s*(.-)%s*$","%1") local rep = i_replacements[str] if rep then texsprint(tex.ctxcatcodes,rep) else texsprint(tex.ctxcatcodes,(str:gsub(".",i_replacements))) end end function table.keys_as_string(t) local k = { } for k,_ in pairs(t) do k[#k+1] = k end return concat(k,"") end --~ local leftdelimiters = "[" .. table.keys_as_string(l_replacements) .. "]" --~ local rightdelimiters = "[" .. table.keys_as_string(r_replacements) .. "]" function lxml.mml.mfenced(id,pattern) -- multiple separators id = lxml.id(id) local left, right, separators = id.at.open or "(", id.at.close or ")", id.at.separators or "," local l, r = l_replacements[left], r_replacements[right] texsprint(tex.ctxcatcodes,"\\enabledelimiter") if l then texsprint(tex.ctxcatcodes,l_replacements[left] or o_replacements[left] or "") else texsprint(tex.ctxcatcodes,o_replacements["@l"]) texsprint(tex.ctxcatcodes,left) end texsprint(tex.ctxcatcodes,"\\disabledelimiter") local n = xml.count(id,pattern) if n == 0 then -- skip elseif n == 1 then lxml.all(id,pattern) else local t = { } for s in utf.gmatch(separators,"[^%s]") do t[#t+1] = s end for i=1,n do lxml.idx(id,pattern,i) -- kind of slow, some day ... if i < n then local m = t[i] or t[#t] or "" if m == "|" then m = "\\enabledelimiter\\middle|\\relax\\disabledelimiter" elseif m == doublebar then m = "\\enabledelimiter\\middle|\\relax\\disabledelimiter" elseif m == "{" then m = "\\{" elseif m == "}" then m = "\\}" end texsprint(tex.ctxcatcodes,m) end end end texsprint(tex.ctxcatcodes,"\\enabledelimiter") if r then texsprint(tex.ctxcatcodes,r_replacements[right] or o_replacements[right] or "") else texsprint(tex.ctxcatcodes,right) texsprint(tex.ctxcatcodes,o_replacements["@r"]) end texsprint(tex.ctxcatcodes,"\\disabledelimiter") end local function flush(e,tag,toggle) -- texsprint(tex.ctxcatcodes,(toggle and "^{") or "_{") if toggle then texsprint(tex.ctxcatcodes,"^{") else texsprint(tex.ctxcatcodes,"_{") end if tag == "none" then texsprint(tex.ctxcatcodes,"{}") else xmlsprint(e.dt) end if not toggle then texsprint(tex.ctxcatcodes,"}") else texsprint(tex.ctxcatcodes,"}{}") end return not toggle end function lxml.mml.mmultiscripts(id) local done, toggle = false, false id = lxml.id(id) -- for i=1,#id.dt do local e = id.dt[i] if type(e) == table then ... for r, d, k in xml.elements(id,"/*") do local e = d[k] local tag = e.tg if tag == "mprescripts" then texsprint(tex.ctxcatcodes,"{}") done = true elseif done then toggle = flush(e,tag,toggle) end end local done, toggle = false, false for r, d, k in xml.elements(id,"/*") do local e = d[k] local tag = e.tg if tag == "mprescripts" then break elseif done then toggle = flush(e,tag,toggle) else xmlsprint(e.dt) done = true end end end local columnalignments = { left = "flushleft", right = "flushright", center = "middle", } local rowalignments = { top = "high", bottom = "low", center = "lohi", baseline = "top", axis = "lohi", } local frametypes = { none = "off", solid = "on", dashed = "on", } -- crazy element ... should be a proper structure instead of such a mess function lxml.mml.mcolumn(root) root = lxml.id(root) local matrix, numbers = { }, 0 local function collect(m,dk) local tag = dk.tg if tag == "mi" or tag == "mn" or tag == "mo" or tag == "mtext" then local str = xml.content(dk) for s in utfcharacters(str) do -- utf.gmatch(str,".") btw, the gmatch was bugged m[#m+1] = { tag, s } end if tag == "mn" then local n = utf.len(str) if n > numbers then numbers = n end end elseif tag == "mspace" or tag == "mline" then local str = dk.at.spacing or "" for s in utfcharacters(str) do -- utf.gmatch(str,".") btw, the gmatch was bugged m[#m+1] = { tag, s } end elseif tag == "mline" then m[#m+1] = { tag, dk } end end for r, d, k in xml.elements(root,"/*") do local m = { } matrix[#matrix+1] = m local dk = d[k] if dk.tg == "mrow" then -- only one level for r, d, k in xml.elements(dk,"/*") do collect(m,d[k]) end else collect(m,dk) end end tex.sprint(tex.ctxcatcodes,"\\halign\\bgroup\\hss$#$&$#$\\cr") for i=1,#matrix do local m = matrix[i] local mline = true for j=1,#m do if m[j][1] ~= "mline" then mline = false break end end if mline then tex.sprint(tex.ctxcatcodes,"\\noalign{\\obeydepth\\nointerlineskip}") end for j=1,#m do local mm = m[j] local tag, chr = mm[1], mm[2] if tag == "mline" then --~ local n, p = true, true --~ for c=1,#matrix do --~ local mc = matrix[c][j] --~ if mc then --~ mc = mc[2] --~ if type(mc) ~= "string" then --~ n, p = false, false --~ break --~ elseif mc:find("^[%d ]$") then -- rangecheck is faster --~ -- digit --~ elseif not mc:find("^[%.%,]$") then -- rangecheck is faster --~ -- punctuation --~ else --~ n = false --~ break --~ end --~ end --~ end --~ if n then --~ chr = "\\mmlmcolumndigitrule" --~ elseif p then --~ chr = "\\mmlmcolumnpunctuationrule" --~ else --~ chr = "\\mmlmcolumnsymbolrule" -- should be widest char --~ end chr = "\\hrulefill" elseif tag == "mspace" then chr = "\\mmlmcolumndigitspace" -- utfchar(0x2007) end if j == numbers + 1 then tex.sprint(tex.ctxcatcodes,"&") end local nchr = n_replacements[chr] tex.sprint(tex.ctxcatcodes,nchr or chr) end tex.sprint(tex.ctxcatcodes,"\\crcr") end tex.sprint(tex.ctxcatcodes,"\\egroup") end local spacesplitter = lpeg.Ct(lpeg.splitat(" ")) function lxml.mml.mtable(root) root = lxml.id(root) -- todo: align, rowspacing, columnspacing, rowlines, columnlines local at = root.at local rowalign = at.rowalign local columnalign = at.columnalign local frame = at.frame local rowaligns = rowalign and spacesplitter:match(rowalign) local columnaligns = columnalign and spacesplitter:match(columnalign) local frames = frame and spacesplitter:match(frame) local framespacing = at.framespacing or "0pt" local framespacing = at.framespacing or "-\\ruledlinewidth" -- make this an option --~ function texsprint(a,b) print(b) end texsprint(tex.ctxcatcodes, format("\\bTABLE[frame=%s,offset=%s]",frametypes[frame or "none"] or "off",framespacing)) for r, d, k in xml.elements(root,"/(mml:mtr|mml:mlabeledtr)") do texsprint(tex.ctxcatcodes,"\\bTR") local dk = d[k] local at = dk.at local col = 0 local rfr = at.frame or (frames and frames [#frames]) local rra = at.rowalign or (rowaligns and rowaligns [#rowaligns]) local rca = at.columnalign or (columnaligns and columnaligns[#columnaligns]) local ignorelabel = dk.tg == "mlabeledtr" for rr, dd, kk in xml.elements(dk,"/mml:mtd") do col = col + 1 if ignorelabel and col == 1 then -- get rid of label, should happen at the document level else local dk = dd[kk] local at = dk.at local rowspan, columnspan = at.rowspan or 1, at.columnspan or 1 local cra = rowalignments [at.rowalign or (rowaligns and rowaligns [col]) or rra or "center"] or "lohi" local cca = columnalignments[at.columnalign or (columnaligns and columnaligns[col]) or rca or "center"] or "middle" local cfr = frametypes [at.frame or (frames and frames [col]) or rfr or "none" ] or "off" texsprint(tex.ctxcatcodes,format("\\bTD[align={%s,%s},frame=%s,nx=%s,ny=%s]$\\ignorespaces",cra,cca,cfr,columnspan,rowspan)) xmlcprint(dk) texsprint(tex.ctxcatcodes,"\\removeunwantedspaces$\\eTD") -- $ end end --~ if dk.tg == "mlabeledtr" then --~ texsprint(tex.ctxcatcodes,"\\bTD") --~ xmlcprint(xml.first(dk,"/!mml:mtd")) --~ texsprint(tex.ctxcatcodes,"\\eTD") --~ end texsprint(tex.ctxcatcodes,"\\eTR") end texsprint(tex.ctxcatcodes, "\\eTABLE") end function lxml.mml.csymbol(root) root = lxml.id(root) local encoding = root.at.encoding or "" local hash = url.hashed(lower(root.at.definitionUrl or "")) local full = hash.original or "" local base = hash.path or "" local text = string.strip(xml.content(root) or "") texsprint(tex.ctxcatcodes,format("\\mmlapplycsymbol{%s}{%s}{%s}{%s}",full,base,encoding,text)) end function lxml.mml.menclosepattern(root) root = lxml.id(root) local a = root.at.notation if a and a ~= "" then texsprint("mml:enclose:"..a:gsub(" +",",mml:enclose:")) end end