use mitex_parser::syntax::{SyntaxElement, SyntaxKind, SyntaxNode};
pub fn sanitize_label(label: &str) -> String {
label.replace([':', ' ', '_'], "-")
}
pub fn to_roman_numeral(num: usize) -> String {
if num == 0 {
return "0".to_string();
}
let values = [
(1000, "M"),
(900, "CM"),
(500, "D"),
(400, "CD"),
(100, "C"),
(90, "XC"),
(50, "L"),
(40, "XL"),
(10, "X"),
(9, "IX"),
(5, "V"),
(4, "IV"),
(1, "I"),
];
let mut result = String::new();
let mut n = num;
for (value, symbol) in values {
while n >= value {
result.push_str(symbol);
n -= value;
}
}
result
}
pub fn protect_zero_arg_commands(input: &str) -> String {
let mut result = input.to_string();
result = result.replace("\\today", "\u{E000}TODAY\u{E001}");
result = result.replace("\\LaTeX", "\u{E000}LATEX\u{E001}");
result = result.replace("\\TeX", "\u{E000}TEX\u{E001}");
result = result.replace("\\XeTeX", "\u{E000}XETEX\u{E001}");
result = result.replace("\\LuaTeX", "\u{E000}LUATEX\u{E001}");
result = result.replace("\\pdfTeX", "\u{E000}PDFTEX\u{E001}");
result = result.replace("\\BibTeX", "\u{E000}BIBTEX\u{E001}");
result
}
pub fn restore_protected_commands(input: &str) -> String {
let mut result = input.to_string();
result = result.replace("\u{E000}TODAY\u{E001}", "#datetime.today().display()");
result = result.replace("\u{E000}LATEX\u{E001}", "LaTeX");
result = result.replace("\u{E000}TEX\u{E001}", "TeX");
result = result.replace("\u{E000}XETEX\u{E001}", "XeTeX");
result = result.replace("\u{E000}LUATEX\u{E001}", "LuaTeX");
result = result.replace("\u{E000}PDFTEX\u{E001}", "pdfTeX");
result = result.replace("\u{E000}BIBTEX\u{E001}", "BibTeX");
result
}
pub fn clean_whitespace(input: &str) -> String {
let mut result = String::new();
let mut consecutive_newlines = 0;
let mut in_code_block = false;
for line in input.lines() {
let trimmed = line.trim_end();
if trimmed.starts_with("```") {
in_code_block = !in_code_block;
result.push_str(line);
result.push('\n');
consecutive_newlines = 1;
continue;
}
if in_code_block {
result.push_str(line);
result.push('\n');
continue;
}
if trimmed.is_empty() {
consecutive_newlines += 1;
if consecutive_newlines <= 2 {
result.push('\n');
}
} else {
result.push_str(trimmed);
result.push('\n');
consecutive_newlines = 1; }
}
let result = result.trim_start_matches('\n').to_string();
let result = result.trim_end().to_string();
if result.is_empty() {
result
} else {
result + "\n"
}
}
pub fn extract_node_text(node: &SyntaxNode) -> String {
let mut text = String::new();
for child in node.children_with_tokens() {
match child {
SyntaxElement::Token(t) => {
if !matches!(
t.kind(),
SyntaxKind::TokenLBrace
| SyntaxKind::TokenRBrace
| SyntaxKind::TokenLBracket
| SyntaxKind::TokenRBracket
) {
text.push_str(t.text());
}
}
SyntaxElement::Node(n) => {
text.push_str(&extract_node_text(&n));
}
}
}
text
}
pub fn extract_node_text_with_braces(node: &SyntaxNode) -> String {
let mut text = String::new();
for child in node.children_with_tokens() {
match child {
SyntaxElement::Token(t) => {
text.push_str(t.text());
}
SyntaxElement::Node(n) => {
text.push_str(&extract_node_text_with_braces(&n));
}
}
}
text
}
pub fn extract_arg_content(node: &SyntaxNode) -> String {
let mut content = String::new();
for child in node.children_with_tokens() {
match child.kind() {
SyntaxKind::TokenLBrace
| SyntaxKind::TokenRBrace
| SyntaxKind::TokenLBracket
| SyntaxKind::TokenRBracket => continue,
SyntaxKind::ItemCurly | SyntaxKind::ItemBracket => {
if let SyntaxElement::Node(n) = child {
content.push_str(&extract_node_text(&n));
}
}
_ => {
if let SyntaxElement::Token(t) = child {
content.push_str(t.text());
} else if let SyntaxElement::Node(n) = child {
content.push_str(&extract_node_text(&n));
}
}
}
}
content.trim().to_string()
}
pub fn extract_arg_content_with_braces(node: &SyntaxNode) -> String {
let mut content = String::new();
for child in node.children_with_tokens() {
match child.kind() {
SyntaxKind::TokenLBrace
| SyntaxKind::TokenRBrace
| SyntaxKind::TokenLBracket
| SyntaxKind::TokenRBracket => continue,
SyntaxKind::ItemCurly | SyntaxKind::ItemBracket => {
if let SyntaxElement::Node(n) = child {
content.push_str(&extract_curly_inner_content(&n));
}
}
_ => {
if let SyntaxElement::Token(t) = child {
content.push_str(t.text());
} else if let SyntaxElement::Node(n) = child {
content.push_str(&extract_node_text_with_braces(&n));
}
}
}
}
content.trim().to_string()
}
pub fn extract_curly_inner_content(node: &SyntaxNode) -> String {
let mut content = String::new();
for child in node.children_with_tokens() {
match child.kind() {
SyntaxKind::TokenLBrace
| SyntaxKind::TokenRBrace
| SyntaxKind::TokenLBracket
| SyntaxKind::TokenRBracket => continue,
_ => {
if let SyntaxElement::Token(t) = child {
content.push_str(t.text());
} else if let SyntaxElement::Node(n) = child {
content.push_str(&extract_node_text_with_braces(&n));
}
}
}
}
content
}
pub fn contains_top_level_separator(text: &str, separator: char) -> bool {
let mut paren_depth = 0usize;
let mut bracket_depth = 0usize;
let mut brace_depth = 0usize;
for ch in text.chars() {
match ch {
'(' => paren_depth += 1,
')' if paren_depth > 0 => paren_depth -= 1,
'[' => bracket_depth += 1,
']' if bracket_depth > 0 => bracket_depth -= 1,
'{' => brace_depth += 1,
'}' if brace_depth > 0 => brace_depth -= 1,
_ => {}
}
if ch == separator && paren_depth == 0 && bracket_depth == 0 && brace_depth == 0 {
return true;
}
}
false
}
pub fn convert_caption_text(text: &str) -> String {
let mut result = String::new();
let mut chars = text.chars().peekable();
while let Some(ch) = chars.next() {
if ch == '$' {
let mut math_content = String::new();
while let Some(&next) = chars.peek() {
if next == '$' {
chars.next(); break;
}
math_content.push(chars.next().unwrap());
}
let converted = super::latex_math_to_typst(&math_content);
result.push('$');
result.push_str(&converted);
result.push('$');
} else if ch == '\\' {
let mut cmd = String::new();
while let Some(&next) = chars.peek() {
if next.is_ascii_alphabetic() {
cmd.push(chars.next().unwrap());
} else {
break;
}
}
let has_arg = crate::data::symbols::is_caption_text_command(&cmd);
let arg_content = if has_arg {
while let Some(&' ') = chars.peek() {
chars.next();
}
if chars.peek() == Some(&'{') {
chars.next(); let mut content = String::new();
let mut brace_depth = 1;
for c in chars.by_ref() {
if c == '{' {
brace_depth += 1;
content.push(c);
} else if c == '}' {
brace_depth -= 1;
if brace_depth == 0 {
break;
}
content.push(c);
} else {
content.push(c);
}
}
Some(content)
} else {
None
}
} else {
None
};
match cmd.as_str() {
"textbf" | "bf" => {
result.push('*');
if let Some(content) = arg_content {
result.push_str(&convert_caption_text(&content));
}
result.push('*');
}
"textit" | "it" | "emph" => {
result.push('_');
if let Some(content) = arg_content {
result.push_str(&convert_caption_text(&content));
}
result.push('_');
}
"texttt" => {
result.push('`');
if let Some(content) = arg_content {
result.push_str(&content); }
result.push('`');
}
"textsc" => {
result.push_str("#smallcaps[");
if let Some(content) = arg_content {
result.push_str(&convert_caption_text(&content));
}
result.push(']');
}
"underline" => {
result.push_str("#underline[");
if let Some(content) = arg_content {
result.push_str(&convert_caption_text(&content));
}
result.push(']');
}
"textrm" | "text" | "mbox" | "hbox" => {
if let Some(content) = arg_content {
result.push_str(&convert_caption_text(&content));
}
}
"textsf" => {
result.push_str("#text(font: \"sans-serif\")[");
if let Some(content) = arg_content {
result.push_str(&convert_caption_text(&content));
}
result.push(']');
}
"today" => result.push_str("#datetime.today().display()"),
"LaTeX" => result.push_str("LaTeX"),
"TeX" => result.push_str("TeX"),
"XeTeX" => result.push_str("XeTeX"),
"LuaTeX" => result.push_str("LuaTeX"),
"pdfTeX" => result.push_str("pdfTeX"),
"BibTeX" => result.push_str("BibTeX"),
"&" => result.push('&'),
"%" => result.push('%'),
"_" => result.push_str("\\_"), "#" => result.push_str("\\#"), "$" => result.push_str("\\$"), "{" => result.push('{'),
"}" => result.push('}'),
"\\" => result.push_str("\\ "), "" => {
}
_ => {
if let Some(content) = arg_content {
result.push_str(&convert_caption_text(&content));
}
}
}
} else {
result.push(ch);
}
}
result
}