What is the best practice to create a very complex string?

I'm trying to create a small program which translate excel file to html String.
I wrote a POC first, and suddenly realized that, I allocated unexpectedly many strings:

fn rendered<T: AsRef<Path>>(p: Result<T, String>) -> String {
    let p = match p {
        Ok(p) => p,
        Err(p) => return p,
    };
    let p = p.as_ref();
    let file_name = p.file_name();
    let p = fs::read(p).unwrap();
    let cursor = io::Cursor::new(p);
    let f = xlsx::read_reader(cursor, true).unwrap();
    return format!(
        "{file_name:?}\n{}",
        f.get_sheet_collection()
            .iter()
            .map(|x| get_sheet(x, f.get_theme()))
            .collect::<Vec<_>>()
            .join(",")
    );
}
// use umya_spreadsheet::structs::Worksheet;
fn get_sheet(x: &Worksheet, theme: &Theme) -> String {
    format!("{}\n{}", x.get_name(), get_content(x, theme))
}
fn get_content(x: &Worksheet, theme: &Theme) -> String {
    format!(
        r##"<table border="1px" style="text-align: center">{}</table>"##,
        (0..x.get_highest_row())
            .map(|idx| format!(
                r##"<tr>{}</tr>"##,
                get_cell_content(x.get_collection_by_row(&idx).into_iter(), theme)
            ))
            .collect::<Vec<_>>()
            .join("\n")
    )
}
fn get_cell_content<'a>(x: impl Iterator<Item = &'a Cell>, theme: &Theme) -> String {
    x.map(|x| {
        format!(
            r##"<td{}>{}</td>"##,
            if let Some(color) = x.get_style().get_background_color() {
                &format!(
                    r##" bgcolor="#{}""##,
                    if color.get_argb_with_theme(theme).len() == 8 {
                        color.get_argb_with_theme(theme)[2..].to_string()
                    } else {
                        color.get_argb_with_theme(theme).to_string()
                    }
                )
            } else {
                ""
            },
            x.get_formatted_value()
        )
    })
    .collect::<Vec<_>>()
    .join("")
}

I know 2 ways to avoid unnecessary allocations:
One of them is use fmt::Write, with either rewrite std::fmt::Display/Debug or just manually execute fmt::Write in the target string, which works but require lots of manual work.
Another of them is use format_args! macro, but this is limited to some simple expressions only.

I wonder is there any method I don't know, or is it worth to invent another method?

1 Like

In the editor there are some formatting controls. The top left one is a toggle to show raw markdown, which should let you fix your formatting (at least when using the mobile site, I have no idea about if there is some app or not).

As it is, this is too hard to read, so I won't even attempt to answer your question.

2 Likes

why not use the write!() macro? it is the usually used to manually implement Display or Debug combined with Formatter, but you can use other "buffer/sink" types too:

// import the `write_fmt()` method for `String`
// note:
// some types should use `std::io::Write` instead,
// such as `StdOut` and `Vec<u8>`, etc.
use std::fmt::Write;
let mut buf = String::new();
let title = "hello world";
let message = "what a lovely day!";
write!(&mut buf, r#"
<html>
<head>
  <title>{title}</title>
</head>
<body>
  <h1>{title}</h1>
  <p>{message}</p>
</body>
</html>
"#);

The write! macro is helpful.

Also check out format_args! and impl Display that let you pass formatted "strings" to functions without making them real strings immediately.

Also consider using some HTML templating engine. Separate building a data model from your excel data, from the logic that makes it HTML-formatted.

2 Likes

As you can see, in order to make program more readable, we may write one formatted string and one write macro.

write!(&mut buf, r##"formatted content"##)

But in my case, since there are lots of seperated parts, if using write!, I'll perhaps got:

fn whole(&mut buffer, data: &Data) {
    write!(&mut buffer, "begin").unwrap();
    write_content(&mut buffer, data: &Data);
    write!(&mut buffer, "end").unwrap();
}
fn write_content(&mut buffer, data: &Data) {
    let lines = data.lines();
    if lines.len() == 0 {return}
    write_lines(&mut buffer, &lines[0])
    for i in &lines[1..] {
        write!(&mut buffer, "\n").unwrap(); // seperator
        write_lines(&mut buffer, i)
    }
}
fn write_lines(...)

the code will be awful.

That’s why people use templates for writing things like html, in Rust as well as other languages.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.