use chrono::{DateTime, Duration, Utc};
pub fn format_document_list_item(
id: &str,
title: &str,
updated_at: DateTime ", "").replace(" {} ", "");
result = result.replace(" ");
in_text = true;
} else if tag == "/text:p" {
html.push_str(¤t_text);
current_text.clear();
html.push_str(" ", " {}", "# ").replace("
", "\n");
md = md.replace("", "## ").replace("
", "\n");
md = md.replace("", "### ").replace("
", "\n");
md = md.replace("", "#### ").replace("
", "\n");
md = md.replace("", "##### ").replace("
", "\n");
md = md.replace("", "###### ").replace("
", "\n");
md = md.replace("
", "\n").replace("
", "\n").replace("
", "\n");
md = md.replace("", "").replace("
", "\n");
md = md.replace("", "").replace("
", "\n");
md = md.replace("", "> ").replace("
", "\n");
md = md.replace("", "`").replace("", "`");
md = md.replace("", "```\n").replace("", "\n```\n");
md = md.replace("
", "\n---\n").replace("
", "\n---\n");
strip_html(&md)
}
pub fn markdown_to_html(md: &str) -> String {
let mut html = String::new();
let lines: Vec<&str> = md.lines().collect();
let mut in_code_block = false;
let mut in_list = false;
for line in lines {
if line.starts_with("```") {
if in_code_block {
html.push_str("");
in_code_block = false;
} else {
html.push_str("");
in_code_block = true;
}
continue;
}
if in_code_block {
html.push_str(&html_escape(line));
html.push('\n');
continue;
}
let processed = process_markdown_line(line);
if line.starts_with("- ") || line.starts_with("* ") {
if !in_list {
html.push_str("");
in_list = true;
}
html.push_str(&format!("
");
in_list = false;
}
html.push_str(&processed);
}
}
if in_list {
html.push_str("");
}
if in_code_block {
html.push_str("");
}
html
}
fn process_markdown_line(line: &str) -> String {
let mut result = line.to_string();
if line.starts_with("# ") {
return format!("{}
", &line[2..]);
} else if line.starts_with("## ") {
return format!("{}
", &line[3..]);
} else if line.starts_with("### ") {
return format!("{}
", &line[4..]);
} else if line.starts_with("#### ") {
return format!("{}
", &line[5..]);
} else if line.starts_with("##### ") {
return format!("{}
", &line[6..]);
} else if line.starts_with("###### ") {
return format!("{}
", &line[7..]);
} else if line.starts_with("> ") {
return format!("{}
", &line[2..]);
} else if line == "---" || line == "***" || line == "___" {
return "
".to_string();
}
result = process_inline_formatting(&result);
if !result.is_empty() && !result.starts_with('<') {
result = format!("$1").to_string();
}
let link_re = regex::Regex::new(r"\[(.+?)\]\((.+?)\)").ok();
if let Some(re) = link_re {
result = re.replace_all(&result, r#"$1"#).to_string();
}
result
}
pub fn count_words(text: &str) -> usize {
let plain_text = strip_html(text);
plain_text
.split_whitespace()
.filter(|s| !s.is_empty())
.count()
}
pub fn truncate_text(text: &str, max_chars: usize) -> String {
if text.len() <= max_chars {
return text.to_string();
}
let truncated: String = text.chars().take(max_chars).collect();
if let Some(last_space) = truncated.rfind(' ') {
format!("{}...", &truncated[..last_space])
} else {
format!("{}...", truncated)
}
}
pub fn sanitize_filename(name: &str) -> String {
name.chars()
.map(|c| {
if c.is_alphanumeric() || c == '-' || c == '_' || c == '.' {
c
} else if c == ' ' {
'_'
} else {
'_'
}
})
.collect::
"),
"tab" => html.push_str(" "),
_ => {}
}
if i < chars.len() && chars[i] == ' ' {
i += 1;
}
continue;
}
'\n' | '\r' => {}
_ => {
if in_group <= 1 {
html.push(ch);
}
}
}
i += 1;
}
if underline {
html.push_str("");
}
if italic {
html.push_str("");
}
if bold {
html.push_str("");
}
html.push_str("
", "\\par\n");
result = result.replace("
", "\\par\n");
result = result.replace("
", "\\par\n");
result = result.replace("", "\\fs48\\b ");
result = result.replace("
", "\\b0\\fs24\\par\n");
result = result.replace("", "\\fs36\\b ");
result = result.replace("
", "\\b0\\fs24\\par\n");
result = result.replace("", "\\fs28\\b ");
result = result.replace("
", "\\b0\\fs24\\par\n");
let stripped = strip_html(&result);
rtf.push_str(&stripped);
rtf.push('}');
rtf
}
pub fn odt_content_to_html(odt_xml: &str) -> String {
let mut html = String::from("
");
} else if tag == "text:tab" || tag == "text:tab/" {
current_text.push_str(" ");
}
} else if in_text {
current_text.push(chars[i]);
}
i += 1;
}
if !current_text.is_empty() {
html.push_str(¤t_text);
}
html.push_str("
", "
", "
", "", "
", "\n");
result = result.replace("", "
", "\n");
result = result.replace("", "
", "\n");
let stripped = strip_html(&result);
let paragraphs: Vec<&str> = stripped.lines().collect();
for para in paragraphs {
if !para.trim().is_empty() {
odt.push_str(&format!("
"))), _ => Err(format!("Unsupported format: {format}")), } }