html-to-markdown-rs 3.9.2

High-performance HTML to Markdown converter using the astral-tl parser. Part of the Xberg ecosystem.
Documentation
// ~keep Rust inner attributes below are crate-level attributes, not a shell shebang.
#![allow(missing_docs)]

fn convert(
    html: &str,
    opts: Option<html_to_markdown_rs::ConversionOptions>,
) -> html_to_markdown_rs::error::Result<String> {
    html_to_markdown_rs::convert(html, opts).map(|r| r.content.unwrap_or_default())
}

use html_to_markdown_rs::ConversionOptions;

#[test]
fn test_br_inside_bold_tags() {
    let html = "<b>Hello!<br/></b><b>Hola!</b>";
    let result = convert(html, None).unwrap();

    assert!(result.contains("**Hello!**  \n**Hola!**"));
    assert!(!result.contains("**Hello!****Hola!**"));
}

#[test]
fn test_br_inside_strong_tags() {
    let html = "<strong>First<br/></strong><strong>Second</strong>";
    let result = convert(html, None).unwrap();

    assert!(result.contains("**First**  \n**Second**"));
    assert!(!result.contains("**First****Second**"));
}

#[test]
fn test_br_between_spans_preserves_hard_break_spaces_issue_432() {
    // ~keep issue #432: the normalized span handler must not strip an intentional
    // ~keep hard break ("  \n") emitted by <br> between two <span> elements.
    let html = "<div><span>First</span><br><span>Second</span></div>";
    let result = convert(html, None).unwrap();

    assert!(
        result.contains("First  \nSecond"),
        "Expected preserved spaces-style hard break: {result:?}"
    );
    assert!(
        !result.contains("First  Second") && !result.contains("FirstSecond"),
        "Hard break must not be collapsed: {result:?}"
    );
}

#[test]
fn test_br_between_spans_preserves_hard_break_backslash_issue_432() {
    let html = "<div><span>First</span><br><span>Second</span></div>";
    let options = ConversionOptions {
        newline_style: html_to_markdown_rs::NewlineStyle::Backslash,
        ..Default::default()
    };
    let result = convert(html, Some(options)).unwrap();

    assert!(
        result.contains("First\\\nSecond"),
        "Expected preserved backslash-style hard break: {result:?}"
    );
    assert!(
        !result.contains("First\\Second") && !result.contains("FirstSecond"),
        "Hard break must not be collapsed: {result:?}"
    );
}

#[test]
fn test_multiple_bolds_with_br() {
    let html = "<b>Line 1<br/></b><b>Line 2<br/></b><b>Line 3</b>";
    let result = convert(html, None).unwrap();

    assert!(result.matches("**").count() >= 6);
    assert!(result.contains("**Line 1**"));
    assert!(result.contains("**Line 2**"));
    assert!(result.contains("**Line 3**"));
    assert!(result.contains("**Line 1**  \n**Line 2**"));
}

#[test]
fn test_br_inside_em_tags() {
    let html = "<em>First<br/></em><em>Second</em>";
    let result = convert(html, None).unwrap();

    assert!(result.contains("*First*  \n*Second*"));
    assert!(!result.contains("*First**Second*"));
}

#[test]
fn test_br_inside_italic_tags() {
    let html = "<i>Alpha<br/></i><i>Beta</i>";
    let result = convert(html, None).unwrap();

    assert!(result.contains("*Alpha*  \n*Beta*"));
    assert!(!result.contains("*Alpha**Beta*"));
}

#[test]
fn test_br_with_backslash_style() {
    let html = "<b>Hello<br/></b><b>World</b>";
    let options = ConversionOptions {
        newline_style: html_to_markdown_rs::NewlineStyle::Backslash,
        ..Default::default()
    };
    let result = convert(html, Some(options)).unwrap();

    assert!(result.contains("**Hello**\\\n**World**"));
    assert!(!result.contains("**Hello****World**"));
}

#[test]
fn test_br_inside_nested_formatting() {
    let html = "<b><i>Bold italic<br/></i></b><b>Just bold</b>";
    let result = convert(html, None).unwrap();

    assert!(result.contains("***Bold italic***  \n**Just bold**"));
}

#[test]
fn test_br_at_end_of_paragraph_with_bold() {
    let html = "<p><b>Line 1<br/></b><b>Line 2</b></p>";
    let result = convert(html, None).unwrap();

    assert!(result.contains("**Line 1**  \n**Line 2**"));
}