1mod latex_parser;
29mod mathml_renderer;
30mod raw_node_slice;
31
32use rustc_hash::FxHashMap;
33#[cfg(feature = "serde")]
34use serde::Deserialize;
35
36use self::latex_parser::{LatexErrKind, NodeRef, Token, node_vec_to_node};
37use self::mathml_renderer::arena::{Arena, FrozenArena};
38use self::mathml_renderer::ast::{MathMLEmitter, Node};
39use self::raw_node_slice::RawNodeSlice;
40
41pub use self::latex_parser::LatexError;
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum MathDisplay {
46 Inline,
48 Block,
50}
51
52#[derive(Debug, Clone, Copy, Default)]
57#[cfg_attr(feature = "serde", derive(Deserialize))]
58#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
59pub enum PrettyPrint {
60 #[default]
62 Never,
63 Always,
65 Auto,
67}
68
69#[derive(Debug, Default)]
104#[cfg_attr(feature = "serde", derive(Deserialize))]
105#[cfg_attr(feature = "serde", serde(default, rename_all = "kebab-case"))]
106pub struct MathCoreConfig {
107 pub pretty_print: PrettyPrint,
109 pub macros: FxHashMap<String, String>,
111}
112
113struct CustomCmds {
114 arena: FrozenArena,
115 slice: RawNodeSlice,
116 map: FxHashMap<String, (usize, usize)>,
117}
118
119impl CustomCmds {
120 pub fn get_command<'config, 'source>(
121 &'config self,
122 command: &'source str,
123 ) -> Option<Token<'source>>
124 where
125 'config: 'source,
126 {
127 let (index, num_args) = *self.map.get(command)?;
128 let nodes = self.slice.lift(&self.arena)?;
129 let node = *nodes.get(index)?;
130 Some(Token::CustomCmd(num_args, NodeRef::new(node)))
131 }
132}
133
134pub struct LatexToMathML {
136 pretty_print: PrettyPrint,
137 equation_count: usize,
139 custom_cmds: Option<CustomCmds>,
140}
141
142impl LatexToMathML {
143 pub fn new(config: &MathCoreConfig) -> Result<Self, LatexError<'_>> {
148 Ok(Self {
149 pretty_print: config.pretty_print,
150 equation_count: 0,
151 custom_cmds: Some(parse_custom_commands(&config.macros)?),
152 })
153 }
154
155 pub const fn const_default() -> Self {
157 Self {
158 pretty_print: PrettyPrint::Never,
159 equation_count: 0,
160 custom_cmds: None,
161 }
162 }
163
164 pub fn convert_with_global_counter<'config, 'source>(
173 &'config mut self,
174 latex: &'source str,
175 display: MathDisplay,
176 ) -> Result<String, LatexError<'source>>
177 where
178 'config: 'source,
179 {
180 convert(
181 latex,
182 display,
183 self.custom_cmds.as_ref(),
184 &mut self.equation_count,
185 self.pretty_print,
186 )
187 }
188
189 #[inline]
208 pub fn convert_with_local_counter<'config, 'source>(
209 &'config self,
210 latex: &'source str,
211 display: MathDisplay,
212 ) -> Result<String, LatexError<'source>>
213 where
214 'config: 'source,
215 {
216 let mut equation_count = 0;
217 convert(
218 latex,
219 display,
220 self.custom_cmds.as_ref(),
221 &mut equation_count,
222 self.pretty_print,
223 )
224 }
225
226 pub fn reset_global_counter(&mut self) {
230 self.equation_count = 0;
231 }
232}
233
234fn convert<'config, 'source>(
235 latex: &'source str,
236 display: MathDisplay,
237 custom_cmds: Option<&'config CustomCmds>,
238 equation_count: &mut usize,
239 pretty_print: PrettyPrint,
240) -> Result<String, LatexError<'source>>
241where
242 'config: 'source,
243{
244 let arena = Arena::new();
245 let ast = parse(latex, &arena, custom_cmds)?;
246
247 let mut output = MathMLEmitter::new(equation_count);
248 match display {
249 MathDisplay::Block => output.push_str("<math display=\"block\">"),
250 MathDisplay::Inline => output.push_str("<math>"),
251 };
252
253 let pretty_print = matches!(pretty_print, PrettyPrint::Always)
254 || (matches!(pretty_print, PrettyPrint::Auto) && display == MathDisplay::Block);
255
256 let base_indent = if pretty_print { 1 } else { 0 };
257 for node in ast {
258 output
259 .emit(node, base_indent)
260 .map_err(|_| LatexError(0, LatexErrKind::RenderError))?;
261 }
262 if pretty_print {
263 output.push('\n');
264 }
265 output.push_str("</math>");
266 Ok(output.into_inner())
267}
268
269fn parse<'config, 'arena, 'source>(
270 latex: &'source str,
271 arena: &'arena Arena,
272 custom_cmds: Option<&'config CustomCmds>,
273) -> Result<Vec<&'arena mathml_renderer::ast::Node<'arena>>, LatexError<'source>>
274where
275 'source: 'arena, 'config: 'source, {
278 let lexer = latex_parser::Lexer::new(latex, false, custom_cmds);
279 let mut p = latex_parser::Parser::new(lexer, arena);
280 let nodes = p.parse()?;
281 Ok(nodes)
282}
283
284fn parse_custom_commands<'source>(
285 macros: &'source FxHashMap<String, String>,
286) -> Result<CustomCmds, LatexError<'source>> {
287 let arena = Arena::new();
288 let mut map = FxHashMap::with_capacity_and_hasher(macros.len(), Default::default());
289 let mut parsed_macros = Vec::with_capacity(macros.len());
290 for (name, definition) in macros.iter() {
291 if !is_valid_macro_name(name) {
292 return Err(LatexError(0, LatexErrKind::InvalidMacroName(&name)));
293 }
294 let lexer = latex_parser::Lexer::new(definition, true, None);
295 let mut p = latex_parser::Parser::new(lexer, &arena);
296 let nodes = p.parse()?;
297 let num_args = p.l.parse_cmd_args.unwrap_or(0);
298
299 let node_ref = node_vec_to_node(&arena, nodes);
300 let index = parsed_macros.len();
301 parsed_macros.push(node_ref);
302 map.insert(name.clone(), (index, num_args));
304 }
305 let slice = RawNodeSlice::from_slice(arena.push_slice(&parsed_macros));
306 Ok(CustomCmds {
307 arena: arena.freeze(),
308 slice,
309 map,
310 })
311}
312
313fn is_valid_macro_name(s: &str) -> bool {
314 if s.is_empty() {
315 return false;
316 }
317 let mut chars = s.chars();
318 match (chars.next(), chars.next()) {
319 (Some(_), None) => true,
321 _ => s.bytes().all(|b| b.is_ascii_alphabetic()),
323 }
324}
325
326#[cfg(test)]
327mod tests {
328 use insta::assert_snapshot;
329
330 use crate::mathml_renderer::ast::MathMLEmitter;
331 use crate::{LatexErrKind, LatexError, LatexToMathML};
332
333 use super::{Arena, parse};
334
335 fn convert_content(latex: &str) -> Result<String, LatexError> {
336 let arena = Arena::new();
337 let nodes = parse(latex, &arena, None)?;
338 let mut equation_count = 0;
339 let mut emitter = MathMLEmitter::new(&mut equation_count);
340 for node in nodes.iter() {
341 emitter
342 .emit(node, 0)
343 .map_err(|_| LatexError(0, LatexErrKind::RenderError))?;
344 }
345 Ok(emitter.into_inner())
346 }
347
348 #[test]
349 fn full_tests() {
350 let problems = [
351 ("empty", r""),
352 ("only_whitespace", r" "),
353 ("starts_with_whitespace", r" x "),
354 ("text", r"\text{hi}xx"),
355 ("text_multi_space", r"\text{x y}"),
356 ("text_no_braces", r"\text x"),
357 ("text_no_braces_space_after", r"\text x y"),
358 ("text_no_braces_more_space", r"\text xx"),
359 ("text_then_space", r"\text{x}~y"),
360 ("text_nested", r"\text{ \text{a}}"),
361 ("text_rq", r"\text{\rq}"),
362 (
363 "text_diacritics",
364 r#"\text{\'{a} \~{a} \.{a} \H{a} \`{a} \={a} \"{a} \v{a} \^{a} \u{a} \r{a} \c{c}}"#,
365 ),
366 ("text_with_escape_brace", r"\text{a\}b}"),
367 ("text_with_weird_o", r"\text{x\o y}"),
368 ("text_with_group", r"\text{x{y}z{}p{}}"),
369 ("text_with_special_symbols", r"\text{':,=-}"),
370 ("textbackslash", r"\text{\textbackslash}"),
371 ("textit", r"\textit{x}"),
372 ("textbf", r"\textbf{x}"),
373 ("textbf_with_digit", r"\textbf{1234}"),
374 ("textbf_with_digit_dot", r"\textbf{1234.}"),
375 ("textbf_with_digit_decimal", r"\textbf{1234.5}"),
376 ("texttt", r"\texttt{x}"),
377 ("mathtt", r"\mathtt{x}"),
378 ("mathtt_with_digit", r"\mathtt2"),
379 ("mathbf_with_digit", r"\mathbf{1234}"),
380 ("mathbf_with_digit_dot", r"\mathbf{1234.}"),
381 ("mathbf_with_digit_decimal", r"\mathbf{1234.5}"),
382 ("integer", r"0"),
383 ("rational_number", r"3.14"),
384 ("long_number", r"3,453,435.3453"),
385 ("number_with_dot", r"4.x"),
386 ("long_sub_super", r"x_{92}^{31415}"),
387 ("single_variable", r"x"),
388 ("greek_letter", r"\alpha"),
389 ("greek_letters", r"\phi/\varphi"),
390 (
391 "greek_letter_tf",
392 r"\Gamma\varGamma\boldsymbol{\Gamma\varGamma}",
393 ),
394 ("greek_letter_boldsymbol", r"\boldsymbol{\alpha}"),
395 ("simple_expression", r"x = 3+\alpha"),
396 ("sine_function", r"\sin x"),
397 ("sine_function_parens", r"\sin(x)"),
398 ("sine_function_sqbrackets", r"\sin[x]"),
399 ("sine_function_brackets", r"\sin\{x\}"),
400 ("square_root", r"\sqrt 2"),
401 ("square_root_without_space", r"\sqrt12"),
402 ("square_root_with_space", r"\sqrt 12"),
403 ("complex_square_root", r"\sqrt{x+2}"),
404 ("cube_root", r"\sqrt[3]{x}"),
405 ("simple_fraction", r"\frac{1}{2}"),
406 ("fraction_without_space", r"\frac12"),
407 ("fraction_with_space", r"\frac 12"),
408 ("slightly_more_complex_fraction", r"\frac{12}{5}"),
409 ("superscript", r"x^2"),
410 ("sub_superscript", r"x^2_3"),
411 ("super_subscript", r"x_3^2"),
412 ("double_subscript", r"g_{\mu\nu}"),
413 ("simple_accent", r"\dot{x}"),
414 ("operator_name", r"\operatorname{sn} x"),
415 ("operator_name_with_spaces", r"\operatorname{ hel lo }"),
416 ("operator_name_with_single_char", r"\operatorname{a}"),
417 ("operator_name_with_space_cmd", r"\operatorname{arg\,max}"),
418 ("simple_binomial_coefficient", r"\binom12"),
419 ("stretchy_parentheses", r"\left( x \right)"),
420 ("stretchy_one-sided_parenthesis", r"\left( x \right."),
421 ("simple_integral", r"\int dx"),
422 ("contour_integral", r"\oint_C dz"),
423 ("simple_overset", r"\overset{n}{X}"),
424 ("integral_with_bounds", r"\int_0^1 dx"),
425 ("integral_with_lower_bound", r"\int_0 dx"),
426 ("integral_with_upper_bound", r"\int^1 dx"),
427 ("integral_with_reversed_bounds", r"\int^1_0 dx"),
428 ("integral_with_complex_bound", r"\int_{0+1}^\infty"),
429 ("integral_with_limits", r"\int\limits_0^1 dx"),
430 ("integral_with_lower_limit", r"\int\limits_0 dx"),
431 ("integral_with_upper_limit", r"\int\limits^1 dx"),
432 ("integral_with_reversed_limits", r"\int\limits^1_0 dx"),
433 ("integral_pointless_limits", r"\int\limits dx"),
434 ("max_with_limits", r"\max\limits_x"),
435 ("bold_font", r"\bm{x}"),
436 ("black_board_font", r"\mathbb{R}"),
437 ("sum_with_special_symbol", r"\sum_{i = 0}^∞ i"),
438 ("sum_with_limit", r"\sum\limits_{i=1}^N"),
439 ("sum_pointless_limits", r"\sum\limits n"),
440 ("product", r"\prod_n n"),
441 ("underscore", r"x\ y"),
442 ("stretchy_brace", r"\left\{ x ( x + 2 ) \right\}"),
443 ("stretchy_bracket", r"\left[ x ( x + 2 ) \right]"),
444 ("matrix", r"\begin{pmatrix} x \\ y \end{pmatrix}"),
445 (
446 "align",
447 r#"\begin{align} f ( x ) &= x^2 + 2 x + 1 \\ &= ( x + 1 )^2\end{align}"#,
448 ),
449 ("align_star", r#"\begin{align*}x&=1\\y=2\end{align*}"#),
450 (
451 "text_transforms",
452 r#"{fi}\ \mathit{fi}\ \mathrm{fi}\ \texttt{fi}"#,
453 ),
454 ("colon_fusion", r"a := 2 \land b :\equiv 3"),
455 (
456 "cases",
457 r"f(x):=\begin{cases}0 &\text{if } x\geq 0\\1 &\text{otherwise.}\end{cases}",
458 ),
459 ("mathstrut", r"\mathstrut"),
460 ("greater_than", r"x > y"),
461 ("text_transform_sup", r"\mathbb{N} \cup \mathbb{N}^+"),
462 ("overbrace", r"\overbrace{a+b+c}^{d}"),
463 ("underbrace", r"\underbrace{a+b+c}_{d}"),
464 ("prod", r"\prod_i \prod^n \prod^n_i \prod_i^n"),
465 (
466 "scriptstyle",
467 r"\sum_{\genfrac{}{}{0pt}{}{\scriptstyle 0 \le i \le m}{\scriptstyle 0 < j < n}} P(i, j)",
468 ),
469 ("genfrac", r"\genfrac(]{0pt}{2}{a+b}{c+d}"),
470 ("genfrac_1pt", r"\genfrac(]{1pt}{2}{a+b}{c+d}"),
471 (
472 "genfrac_1pt_with_space",
473 r"\genfrac(]{ 1pt }{2}{a+b}{c+d}",
474 ),
475 ("genfrac_0.4pt", r"\genfrac(]{0.4pt}{2}{a+b}{c+d}"),
476 ("genfrac_0.4ex", r"\genfrac(]{0.4ex}{2}{a+b}{c+d}"),
477 ("genfrac_4em", r"\genfrac(]{4em}{2}{a+b}{c+d}"),
478 ("not_subset", r"\not\subset"),
479 ("not_less_than", r"\not\lt"),
480 ("not_less_than_symbol", r"\not< x"),
481 ("mathrm_with_superscript", r"\mathrm{x}^2"),
482 ("mathrm_with_sin", r"\mathrm{x\sin}"),
483 ("mathrm_with_sin2", r"\mathrm{\sin x}"),
484 ("mathrm_no_brackets", r"\mathrm x"),
485 ("mathit_no_brackets", r"\mathit x"),
486 ("mathbb_no_brackets", r"\mathbb N"),
487 ("mathit_of_max", r"\mathit{ab \max \alpha\beta}"),
488 ("mathit_of_operatorname", r"\mathit{a\operatorname{bc}d}"),
489 ("nested_transform", r"\mathit{\mathbf{a}b}"),
490 ("mathrm_nested", r"\mathit{\mathrm{a}b}"),
491 ("mathrm_nested2", r"\mathrm{\mathit{a}b}"),
492 ("mathrm_nested3", r"\mathrm{ab\mathit{cd}ef}"),
493 ("mathrm_nested4", r"\mathit{\mathrm{a}}"),
494 ("mathrm_multiletter", r"\mathrm{abc}"),
495 (
496 "complicated_operatorname",
497 r"\operatorname {{\pi} o \Angstrom a}",
498 ),
499 ("operatorname_with_other_operator", r"x\operatorname{\max}"),
500 (
501 "continued_fraction",
502 r"a_0 + \cfrac{1}{a_1 + \cfrac{1}{a_2 + \cfrac{1}{a_3 + \cfrac{1}{a_4}}}}",
503 ),
504 ("standalone_underscore", "_2F_3"),
505 ("really_standalone_underscore", "_2"),
506 ("standalone_superscript", "^2F_3"),
507 ("really_standalone_superscript", "^2"),
508 ("prime", r"f'"),
509 ("double_prime", r"f''"),
510 ("triple_prime", r"f'''"),
511 ("quadruple_prime", r"f''''"),
512 ("quintuple_prime", r"f'''''"),
513 ("prime_alone", "'"),
514 ("prime_and_super", r"f'^2"),
515 ("sub_prime_super", r"f_3'^2"),
516 ("double_prime_and_super", r"f''^2"),
517 ("double_prime_and_super_sub", r"f''^2_3"),
518 ("double_prime_and_sub_super", r"f''_3^2"),
519 ("sum_prime", r"\sum'"),
520 ("int_prime", r"\int'"),
521 ("vec_prime", r"\vec{x}'"),
522 ("overset_with_prime", r"\overset{!}{=}'"),
523 ("overset_prime", r"\overset{'}{=}"),
524 ("overset_plus", r"\overset{!}{+}"),
525 ("int_limit_prime", r"\int\limits'"),
526 ("prime_command", r"f^\prime"),
527 ("prime_command_braces", r"f^{\prime}"),
528 ("transform_group", r"\mathit{a{bc}d}"),
529 ("nabla_in_mathbf", r"\mathbf{\nabla} + \nabla"),
530 ("mathcal_vs_mathscr", r"\mathcal{A}, \mathscr{A}"),
531 ("vertical_line", r"P(x|y)"),
532 ("mid", r"P(x\mid y)"),
533 ("special_symbols", r"\%\$\#"),
534 ("lbrack_instead_of_bracket", r"\sqrt\lbrack 4]{2}"),
535 ("middle_vert", r"\left(\frac12\middle|\frac12\right)"),
536 (
537 "middle_uparrow",
538 r"\left(\frac12\middle\uparrow\frac12\right)",
539 ),
540 ("middle_bracket", r"\left(\frac12\middle]\frac12\right)"),
541 ("left_right_different_stretch", r"\left/\frac12\right)"),
542 ("RR_command", r"\RR"),
543 ("odv", r"\odv{f}{x}"),
544 ("xrightarrow", r"\xrightarrow{x}"),
545 ("slashed", r"\slashed{\partial}"),
546 ("plus_after_equal", r"x = +4"),
547 ("equal_after_plus", r"x+ = 4"),
548 ("plus_in_braces", r"4{+}4"),
549 ("equal_at_group_begin", r"x{=x}"),
550 ("plus_after_equal_subscript", r"x =_+4"),
551 ("plus_after_equal_subscript2", r"x =_2 +4"),
552 ("equal_equal", r"4==4"),
553 ("subscript_equal_equal", r"x_==4"),
554 ("color", r"{\color{Blue}x^2}"),
555 ("hspace", r"\hspace{1cm}"),
556 ("hspace_whitespace", r"\hspace{ 4em }"),
557 ("hspace_whitespace_in_between", r"\hspace{ 4 em }"),
558 ("array_simple", r"\begin{array}{lcr} 0 & 1 & 2 \end{array}"),
559 (
560 "array_lines",
561 r"\begin{array}{ |l| |rc| } 10 & 20 & 30\\ 4 & 5 & 6 \end{array}",
562 ),
563 (
564 "array_many_lines",
565 r"\begin{array}{ ||::|l } 10\\ 2 \end{array}",
566 ),
567 (
568 "subarray",
569 r"\sum_{\begin{subarray}{c} 0 \le i \le m\\ 0 < j < n \end{subarray}}",
570 ),
571 ];
572
573 let config = crate::MathCoreConfig {
574 pretty_print: crate::PrettyPrint::Always,
575 ..Default::default()
576 };
577 let converter = LatexToMathML::new(&config).unwrap();
578 for (name, problem) in problems.into_iter() {
579 let mathml = converter
580 .convert_with_local_counter(problem, crate::MathDisplay::Inline)
581 .expect(format!("failed to convert `{}`", problem).as_str());
582 assert_snapshot!(name, &mathml, problem);
583 }
584 }
585
586 #[test]
587 fn error_test() {
588 let problems = [
589 ("end_without_open", r"\end{matrix}"),
590 ("curly_close_without_open", r"}"),
591 ("unsupported_command", r"\asdf"),
592 (
593 "unsupported_environment",
594 r"\begin{xmatrix} 1 \end{xmatrix}",
595 ),
596 ("incorrect_bracket", r"\operatorname[lim}"),
597 ("unclosed_bracket", r"\sqrt[lim"),
598 ("mismatched_begin_end", r"\begin{matrix} 1 \end{bmatrix}"),
599 (
600 "spaces_in_env_name",
601 r"\begin{ pmatrix } x \\ y \end{pmatrix}",
602 ),
603 (
604 "incorrect_bracket_in_begin",
605 r"\begin{matrix] 1 \end{matrix}",
606 ),
607 ("incomplete_sup", r"x^"),
608 ("invalid_sup", r"x^^"),
609 ("invalid_sub_sup", r"x^_"),
610 ("double_sub", r"x__3"),
611 ("int_double_sub", r"\int__3 x dx"),
612 ("unicode_command", r"\éx"),
613 ("wrong_opening_paren", r"\begin[matrix} x \end{matrix}"),
614 ("unclosed_brace", r"{"),
615 ("unclosed_left", r"\left( x"),
616 ("unclosed_env", r"\begin{matrix} x"),
617 ("unclosed_text", r"\text{hello"),
618 ("unexpected_limits", r"\text{hello}\limits_0^1"),
619 ("unsupported_not", r"\not\text{hello}"),
620 ("text_with_unclosed_group", r"\text{x{}"),
621 ("operatorname_with_end", r"\operatorname{\end{matrix}}"),
622 ("operatorname_with_begin", r"\operatorname{\begin{matrix}}"),
623 ("super_then_prime", "f^2'"),
624 ("sub_super_then_prime", "f_5^2'"),
625 ("sup_sup", "x^2^3 y"),
626 ("sub_sub", "x_2_3 y"),
627 ("no_rbrack_instead_of_bracket", r"\sqrt[3\rbrack{1}"),
628 ("genfrac_wrong_unit", r"\genfrac(]{1pg}{2}{a+b}{c+d}"),
629 ("hspace_empty", r"\hspace{ }"),
630 ("hspace_unknown_unit", r"\hspace{2ly}"),
631 ("hspace_non_digits", r"\hspace{2b2cm}"),
632 ("hspace_non_ascii", r"\hspace{22öm}"),
633 ];
634
635 for (name, problem) in problems.into_iter() {
636 let LatexError(loc, error) = convert_content(problem).unwrap_err();
637 let output = format!("Position: {}\n{:#?}", loc, error);
638 assert_snapshot!(name, &output, problem);
639 }
640 }
641
642 #[test]
643 fn test_custom_cmd_zero_arg() {
644 let macros = [
645 ("half".to_string(), r"\frac{1}{2}".to_string()),
646 ("mycmd".to_string(), r"\sqrt{3}".to_string()),
647 ]
648 .into_iter()
649 .collect();
650
651 let config = crate::MathCoreConfig {
652 macros,
653 pretty_print: crate::PrettyPrint::Always,
654 };
655
656 let converter = LatexToMathML::new(&config).unwrap();
657
658 let latex = r"x = \half";
659 let mathml = converter
660 .convert_with_local_counter(latex, crate::MathDisplay::Inline)
661 .unwrap();
662
663 assert_snapshot!("custom_cmd_zero_arg", mathml, latex);
664 }
665 #[test]
666 fn test_custom_cmd_one_arg() {
667 let macros = [
668 ("half".to_string(), r"\frac{1}{2}".to_string()),
669 ("mycmd".to_string(), r"\sqrt{#1}".to_string()),
670 ]
671 .into_iter()
672 .collect();
673
674 let config = crate::MathCoreConfig {
675 macros,
676 pretty_print: crate::PrettyPrint::Always,
677 };
678
679 let converter = LatexToMathML::new(&config).unwrap();
680
681 let latex = r"x = \mycmd{3}";
682 let mathml = converter
683 .convert_with_local_counter(latex, crate::MathDisplay::Inline)
684 .unwrap();
685
686 assert_snapshot!("custom_cmd_one_arg", mathml, latex);
687 }
688}