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
10
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
25
26local context = context
27
28local ctx_enabledelimiter = context.enabledelimiter
29local ctx_disabledelimiter = context.disabledelimiter
30local ctx_xmlflush = context.xmlflush
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
60
61
62
63
64
65
66
67local doublebar = utfchar(0x2016)
68
69local n_replacements = {
70
71 ["."] = "{.}",
72 [","] = "{,}",
73 [" "] = "",
74}
75
76local l_replacements = {
77 ["|"] = "\\mmlleftdelimiter\\vert",
78 ["{"] = "\\mmlleftdelimiter\\lbrace",
79 ["("] = "\\mmlleftdelimiter(",
80 ["["] = "\\mmlleftdelimiter[",
81 ["<"] = "\\mmlleftdelimiter<",
82 [doublebar] = "\\mmlleftdelimiter\\Vert",
83}
84local r_replacements = {
85 ["|"] = "\\mmlrightdelimiter\\vert",
86 ["}"] = "\\mmlrightdelimiter\\rbrace",
87 [")"] = "\\mmlrightdelimiter)",
88 ["]"] = "\\mmlrightdelimiter]",
89 [">"] = "\\mmlrightdelimiter>",
90 [doublebar] = "\\mmlrightdelimiter\\Vert",
91}
92
93
94
95
96
97local o_replacements = {
98 ["@l"] = "\\mmlleftdelimiter.",
99 ["@r"] = "\\mmlrightdelimiter.",
100 ["{"] = "\\mmlleftdelimiter \\lbrace",
101 ["}"] = "\\mmlrightdelimiter\\rbrace",
102 ["|"] = "\\mmlleftorrightdelimiter\\vert",
103
104 ["/"] = "\\mmlleftorrightdelimiter\\solidus",
105 [doublebar] = "\\mmlleftorrightdelimiter\\Vert",
106 ["("] = "\\mmlleftdelimiter(",
107 [")"] = "\\mmlrightdelimiter)",
108 ["["] = "\\mmlleftdelimiter[",
109 ["]"] = "\\mmlrightdelimiter]",
110
111
112 ["#"] = "\\mmlchar{35}",
113 ["$"] = "\\mmlchar{36}",
114 ["%"] = "\\mmlchar{37}",
115 ["&"] = "\\mmlchar{38}",
116 ["^"] = "\\mmlchar{94}{}",
117 ["_"] = "\\mmlchar{95}{}",
118 ["~"] = "\\mmlchar{126}",
119 [" "] = "",
120 ["°"] = "^\\circ",
121
122
123 [utfchar(0xF1026)] = "\\mmlchar{38}",
124 [utfchar(0x02061)] = "",
125
126
127}
128
129local simpleoperatorremapper = utf.remapper(o_replacements)
130
131
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
194
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
219
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
232 },
233 logic1 = {
234 equivalent = "equivalent",
235 ["not"] = "not",
236 ["and"] = "and",
237
238 ["xor"] = "xor",
239
240 ["or"] = "or",
241
242 implies = "implies",
243 ["true"] = "true",
244 ["false"] = "false",
245 },
246 nums1 = {
247
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
271 set = "set",
272 intersect = "intersect",
273
274 union = "union",
275
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
294 partialdiff = "partialdiff",
295 int = "int",
296
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
311
312
313 },
314 rounding1 = {
315 ceiling = "ceiling",
316 floor = "floor",
317
318
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
330 real = "real",
331 imaginary = "imaginary",
332
333 argument = "arg",
334 conjugate = "conjugate",
335 },
336 interval1 = {
337
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
346
347
348 },
349 minmax1 = {
350 min = "min",
351
352 max = "max",
353
354 },
355 piece1 = {
356 piecewise = "piecewise",
357 piece = "piece",
358 otherwise = "otherwise",
359 },
360 error1 = {
361
362
363
364 },
365 limit1 = {
366
367
368
369
370
371 tendsto = "tendsto",
372 },
373 list1 = {
374
375
376
377 },
378 multiset1 = {
379 size = { tag = "card", type = "multiset" },
380 cartesian_product = { tag = "cartesianproduct", type = "multiset" },
381 empty_set = { tag = "emptyset", type = "multiset" },
382
383 intersect = { tag = "intersect", type = "multiset" },
384
385 union = { tag = "union", type = "multiset" },
386
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
401
402
403
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
518
519
520
521
522
523
524
525
526
527
528function mathml.mn(id,pattern)
529
530
531 ctx_mn(lpegmatch(p_mn,xmlcontent(getid(id)) or ""))
532end
533
534
535
536
537
538
539
540function mathml.mo(id)
541 local str = lpegmatch(p_strip,xmlcontent(getid(id)) or "")
542 context(simpleoperatorremapper(str) or str)
543end
544
545function mathml.mi(id)
546
547 local e = getid(id)
548 local str = e.dt
549 if type(str) == "table" then
550 local n = #str
551 if n == 0 then
552
553 elseif n == 1 then
554 local first = str[1]
555 if type(first) == "string" then
556
557
558
559
560
561 local str = lpegmatch(p_strip,first)
562 local rep = i_replacements[str] or lpegmatch(p_mi,str)
563 context(rep)
564
565 else
566 ctx_xmlflush(id)
567 end
568 else
569 ctx_xmlflush(id)
570 end
571 else
572 ctx_xmlflush(id)
573 end
574end
575
576function mathml.mfenced(id)
577 id = getid(id)
578 local at = id.at
579 local left = at.open or "("
580 local right = at.close or ")"
581 local separators = at.separators or ","
582 local l = l_replacements[left]
583 local r = r_replacements[right]
584 ctx_enabledelimiter()
585 if l then
586 context(l_replacements[left] or o_replacements[left] or "")
587 else
588 context(o_replacements["@l"])
589 context(left)
590 end
591 ctx_disabledelimiter()
592 local collected = lxmlfilter(id,"/*")
593 if collected then
594 local n = #collected
595 if n == 0 then
596
597 elseif n == 1 then
598 xmlsprint(collected[1])
599 else
600 local t = utfsplit(separators,true)
601 for i=1,n do
602 xmlsprint(collected[i])
603 if i < n then
604 local m = t[i] or t[#t] or ""
605 if m == "|" then
606 m = "\\enabledelimiter\\middle|\\relax\\disabledelimiter"
607 elseif m == doublebar then
608 m = "\\enabledelimiter\\middle|\\relax\\disabledelimiter"
609 elseif m == "{" then
610 m = "\\{"
611 elseif m == "}" then
612 m = "\\}"
613 end
614 context(m)
615 end
616 end
617 end
618 end
619 ctx_enabledelimiter()
620 if r then
621 context(r_replacements[right] or o_replacements[right] or "")
622 else
623 context(right)
624 context(o_replacements["@r"])
625 end
626 ctx_disabledelimiter()
627end
628
629local function flush(e,tag,toggle)
630 if tag == "none" then
631
632 context("{}")
633
634 elseif toggle then
635 context("^{")
636 xmlsprint(e.dt)
637 context("}{}")
638 else
639 context("_{")
640 xmlsprint(e.dt)
641 context("}")
642 end
643 return not toggle
644end
645
646function mathml.mmultiscripts(id)
647 local done, toggle = false, false
648 for e in lxmlcollected(id,"/*") do
649 local tag = e.tg
650 if tag == "mprescripts" then
651 context("{}")
652 done = true
653 elseif done then
654 toggle = flush(e,tag,toggle)
655 end
656 end
657 local done, toggle = false, false
658 for e in lxmlcollected(id,"/*") do
659 local tag = e.tg
660 if tag == "mprescripts" then
661 break
662 elseif done then
663 toggle = flush(e,tag,toggle)
664 else
665 xmlsprint(e)
666 done = true
667 end
668 end
669end
670
671local columnalignments = {
672 left = "flushleft",
673 right = "flushright",
674 center = "middle",
675}
676
677local rowalignments = {
678 top = "high",
679 bottom = "low",
680 center = "lohi",
681 baseline = "top",
682 axis = "lohi",
683}
684
685local frametypes = {
686 none = "off",
687 solid = "on",
688 dashed = "on",
689}
690
691
692
693function mathml.mcolumn(root)
694 root = getid(root)
695 local matrix, numbers = { }, 0
696 local function collect(m,e)
697 local tag = e.tg
698 if tag == "mi" or tag == "mn" or tag == "mo" or tag == "mtext" then
699 local str = xmltext(e)
700 str = lpegmatch(p_strip,str)
701 for s in utfcharacters(str) do
702 m[#m+1] = { tag, s }
703 end
704 if tag == "mn" then
705 local n = utflen(str)
706 if n > numbers then
707 numbers = n
708 end
709 end
710 elseif tag == "mspace" or tag == "mline" then
711 local str = e.at.spacing or ""
712 for s in utfcharacters(str) do
713 m[#m+1] = { tag, s }
714 end
715
716
717 end
718 end
719 for e in lxmlcollected(root,"/*") do
720 local m = { }
721 matrix[#matrix+1] = m
722 if e.tg == "mrow" then
723
724 for e in lxmlcollected(e,"/*") do
725 collect(m,e)
726 end
727 else
728 collect(m,e)
729 end
730 end
731 ctx_halign()
732 ctx_bgroup()
733 context([[\hss\startimath\alignmark\stopimath\aligntab\startimath\alignmark\stopimath\cr]])
734 for i=1,#matrix do
735 local m = matrix[i]
736 local mline = true
737 for j=1,#m do
738 if m[j][1] ~= "mline" then
739 mline = false
740 break
741 end
742 end
743 if mline then
744 ctx_noalign([[\obeydepth\nointerlineskip]])
745 end
746 for j=1,#m do
747 local mm = m[j]
748 local tag, chr = mm[1], mm[2]
749 if tag == "mline" then
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777 chr = "\\hrulefill"
778 elseif tag == "mspace" then
779 chr = "\\mmlmcolumndigitspace"
780 end
781 if j == numbers + 1 then
782 context("\\aligntab")
783 end
784 local nchr = n_replacements[chr]
785 context(nchr or chr)
786 end
787 ctx_crcr()
788 end
789 ctx_egroup()
790end
791
792local spacesplitter = lpeg.tsplitat(" ")
793
794function mathml.mtable(root)
795
796 root = getid(root)
797 local at = root.at
798 local rowalign = at.rowalign
799 local columnalign = at.columnalign
800 local frame = at.frame
801 local rowaligns = rowalign and lpegmatch(spacesplitter,rowalign)
802 local columnaligns = columnalign and lpegmatch(spacesplitter,columnalign)
803 local frames = frame and lpegmatch(spacesplitter,frame)
804 local framespacing = at.framespacing or "0pt"
805 local framespacing = at.framespacing or "-\\ruledlinewidth"
806
807 ctx_bTABLE { frame = frametypes[frame or "none"] or "off", offset = framespacing, background = "" }
808 for e in lxmlcollected(root,"/(mml:mtr|mml:mlabeledtr)") do
809 ctx_bTR()
810 local at = e.at
811 local col = 0
812 local rfr = at.frame or (frames and frames [#frames])
813 local rra = at.rowalign or (rowaligns and rowaligns [#rowaligns])
814 local rca = at.columnalign or (columnaligns and columnaligns[#columnaligns])
815 local ignorelabel = e.tg == "mlabeledtr"
816 for e in lxmlcollected(e,"/mml:mtd") do
817 col = col + 1
818 if ignorelabel and col == 1 then
819
820 else
821 local at = e.at
822 local rowspan = at.rowspan or 1
823 local columnspan = at.columnspan or 1
824 local cra = rowalignments [at.rowalign or (rowaligns and rowaligns [col]) or rra or "center"] or "lohi"
825 local cca = columnalignments[at.columnalign or (columnaligns and columnaligns[col]) or rca or "center"] or "middle"
826 local cfr = frametypes [at.frame or (frames and frames [col]) or rfr or "none" ] or "off"
827 ctx_bTD { align = formatters["{%s,%s}"](cra,cca), frame = cfr, nx = columnspan, ny = rowspan }
828 if xmlempty(e,".") then
829
830 else
831 ctx_startimath()
832
833 xmlcprint(e)
834
835 ctx_stopimath()
836 end
837 ctx_eTD()
838 end
839 end
840
841
842
843
844
845 ctx_eTR()
846 end
847 ctx_eTABLE()
848end
849
850function mathml.csymbol(root)
851 root = getid(root)
852 local at = root.at
853 local encoding = at.encoding or ""
854 local hash = url.hashed(lower(at.definitionUrl or ""))
855 local full = hash.original or ""
856 local base = hash.path or ""
857 local text = strip(xmltext(root) or "")
858 ctx_mmlapplycsymbol(full,base,encoding,text)
859end
860
861local p = lpeg.Cs(((1-lpegpatterns.whitespace)^1 / "mml:enclose:%0" + (lpegpatterns.whitespace^1)/",")^1)
862
863function mathml.menclosepattern(root)
864 root = getid(root)
865 local a = root.at.notation
866 if a and a ~= "" then
867 context(lpegmatch(p,a))
868 end
869end
870
871function xml.is_element(e,name)
872 return type(e) == "table" and (not name or e.tg == name)
873end
874
875function mathml.cpolar(root)
876 root = getid(root)
877 local dt = root.dt
878 ctx_mathopnolimits("Polar")
879 ctx_left(false,"(")
880 for k=1,#dt do
881 local dk = dt[k]
882 if xml.is_element(dk,"sep") then
883 context(",")
884 else
885 xmlsprint(dk)
886 end
887 end
888 ctx_right(false,")")
889end
890
891
892
893local mathmleq = {
894 [utfchar(0x00AF)] = utfchar(0x203E),
895}
896
897function mathml.extensible(chr)
898 context(mathmleq[chr] or chr)
899end
900 |