bibtex-parser 0.2.2

BibTeX parser for Rust
Documentation
use bibtex_parser::{
    document_to_string, selected_entries_to_string, EntryType, Library, Parser, Value,
};
use std::borrow::Cow;

#[test]
fn key_type_field_and_value_edits_preserve_unrelated_raw_text() {
    let input = "@Article{old_key,\n  TITLE = \"Old\",\n  year = 2024\n}";
    let mut document = Parser::new().preserve_raw().parse_document(input).unwrap();
    let entry = &mut document.entries_mut()[0];

    entry.rename_key("new_key");
    entry.set_entry_type(EntryType::Book);
    assert_eq!(entry.rename_field("TITLE", "title"), 1);
    assert!(entry.replace_field_value("title", Value::from_plain_string("New")));

    let output = document_to_string(&document).unwrap();
    assert_eq!(output, "@book{new_key,\n  title = {New},\n  year = 2024\n}");
    assert!(Library::parse(&output).is_ok());
}

#[test]
fn duplicate_field_occurrences_are_addressable_without_collapsing() {
    let input = "@article{paper,\n  tag = \"a\",\n  tag = \"b\",\n  tag = \"c\"\n}";
    let mut document = Parser::new().preserve_raw().parse_document(input).unwrap();
    assert!(document.entries_mut()[0].replace_field_value_at(
        "tag",
        1,
        Value::from_plain_string("beta")
    ));

    let output = document_to_string(&document).unwrap();
    assert_eq!(
        output,
        "@article{paper,\n  tag = \"a\",\n  tag = {beta},\n  tag = \"c\"\n}"
    );

    assert!(document.entries_mut()[0].remove_field_at("tag", 0));
    let output = document_to_string(&document).unwrap();
    assert!(output.contains("tag = {beta}"));
    assert!(output.contains("tag = {c}"));
    assert!(!output.contains("tag = {a}"));
    assert_eq!(
        Library::parse(&output).unwrap().entries()[0].fields.len(),
        2
    );
}

#[test]
fn add_and_remove_fields_write_a_valid_structured_entry() {
    let input = "@article{paper, title = \"Fast\", file = \"local.pdf\"}";
    let mut document = Parser::new().preserve_raw().parse_document(input).unwrap();
    let entry = &mut document.entries_mut()[0];

    entry.add_field("note", Value::Literal(Cow::Borrowed("accepted")));
    assert_eq!(entry.remove_field("file"), 1);

    let output = document_to_string(&document).unwrap();
    let reparsed = Library::parse(&output).unwrap();
    let entry = &reparsed.entries()[0];
    assert_eq!(entry.get("title"), Some("Fast"));
    assert_eq!(entry.get("note"), Some("accepted"));
    assert!(entry.field("file").is_none());
}

#[test]
fn configured_export_fields_can_be_removed_across_document() {
    let input = r#"@article{a, title = "A", File = "a.pdf"}
@book{b, title = "B", timestamp = "2026"}"#;
    let mut document = Parser::new().preserve_raw().parse_document(input).unwrap();

    assert_eq!(document.remove_export_fields(&["file", "timestamp"]), 2);

    let output = document_to_string(&document).unwrap();
    let reparsed = Library::parse(&output).unwrap();
    assert!(reparsed.entries()[0].field_ignore_case("file").is_none());
    assert!(reparsed.entries()[1]
        .field_ignore_case("timestamp")
        .is_none());
}

#[test]
fn document_level_key_rename_finds_entries_by_key() {
    let input = "@article{old, title = \"A\"}";
    let mut document = Parser::new().preserve_raw().parse_document(input).unwrap();

    assert!(document.rename_key("old", "new"));
    assert!(document.entry_mut_by_key("new").is_some());

    let output = document_to_string(&document).unwrap();
    assert!(output.starts_with("@article{new,"));
    assert!(Library::parse(&output)
        .unwrap()
        .find_by_key("new")
        .is_some());
}

#[test]
fn selected_entries_are_serialized_in_source_order() {
    let input = r#"@article{a, title = "A"}
@comment{skip}
@book{b, title = "B"}
@misc{c, title = "C"}"#;
    let document = Parser::new().preserve_raw().parse_document(input).unwrap();

    let output = selected_entries_to_string(&document, &["c", "a"]).unwrap();
    assert_eq!(
        output,
        r#"@article{a, title = "A"}
@misc{c, title = "C"}"#
    );
}