bibtex-parser 0.3.1

BibTeX parser for Rust
Documentation
use bibtex_parser::{ExpansionOptions, Library, Parser, UnresolvedVariablePolicy, Value};
use std::borrow::Cow;

#[test]
fn parsed_document_exposes_raw_parsed_and_requested_expanded_views() {
    let input = r#"
@string{venue = "VLDB"}
@article{paper,
  title = venue # " 2026",
  month = jan,
  note = missing
}
"#;

    let document = Parser::new().preserve_raw().parse_document(input).unwrap();
    let entry = &document.entries()[0];
    let title = &entry.fields[0].value;
    let month = &entry.fields[1].value;
    let note = &entry.fields[2].value;

    assert_eq!(title.raw_text(), Some(r#"venue # " 2026""#));
    assert!(matches!(title.parsed(), Value::Concat(_)));
    assert_eq!(title.expanded_text(), None);
    assert_eq!(
        document
            .expand_value(title.parsed(), ExpansionOptions::default())
            .unwrap(),
        "VLDB 2026"
    );
    assert_eq!(
        document
            .expand_value(month.parsed(), ExpansionOptions::default())
            .unwrap(),
        "January"
    );

    let preserve = ExpansionOptions {
        unresolved_variables: UnresolvedVariablePolicy::Preserve,
        ..ExpansionOptions::default()
    };
    assert_eq!(
        document.expand_value(note.parsed(), preserve).unwrap(),
        "missing"
    );

    let placeholder = ExpansionOptions {
        unresolved_variables: UnresolvedVariablePolicy::Placeholder,
        ..ExpansionOptions::default()
    };
    assert_eq!(
        document.expand_value(note.parsed(), placeholder).unwrap(),
        "{undefined:missing}"
    );
    assert!(document
        .expand_value(note.parsed(), ExpansionOptions::default())
        .is_err());
}

#[test]
fn parser_can_populate_expanded_values_on_request() {
    let input = r#"
@string{venue = "VLDB"}
@article{paper, title = venue # " 2026", month = jan}
"#;

    let document = Parser::new()
        .preserve_raw()
        .expand_values()
        .parse_document(input)
        .unwrap();
    let entry = &document.entries()[0];

    assert_eq!(entry.fields[0].value.expanded_text(), Some("VLDB 2026"));
    assert_eq!(entry.fields[1].value.expanded_text(), Some("January"));
    assert_eq!(entry.fields[0].value.raw_text(), Some(r#"venue # " 2026""#));
}

#[test]
fn public_value_text_projections_cover_core_value_shapes() {
    let literal = Value::Literal(Cow::Borrowed("A Title"));
    let number = Value::Number(2026);
    let variable = Value::Variable(Cow::Borrowed("venue"));
    let concat = Value::Concat(
        vec![
            Value::Variable(Cow::Borrowed("venue")),
            Value::Literal(Cow::Borrowed(" 2026")),
        ]
        .into_boxed_slice(),
    );

    assert_eq!(literal.to_plain_string(), "A Title");
    assert_eq!(number.to_plain_string(), "2026");
    assert_eq!(variable.to_plain_string(), "venue");
    assert_eq!(variable.to_lossy_string(), "{venue}");
    assert_eq!(concat.to_plain_string(), "venue 2026");
    assert_eq!(Value::from_plain_string("plain").as_str(), Some("plain"));
    assert_eq!(concat.to_bibtex_source(), "venue # { 2026}");
}

#[test]
fn semantic_value_projection_deindents_wrapped_lines_without_touching_raw_text() {
    let input = r#"
@article{paper,
  title = {First line
                  Second line
                  Third line}
}
"#;

    let document = Parser::new().preserve_raw().parse_document(input).unwrap();
    let title = &document.entries()[0].fields[0].value;

    assert_eq!(
        title.raw_text(),
        Some("{First line\n                  Second line\n                  Third line}")
    );
    assert_eq!(title.plain_text(), "First line\nSecond line\nThird line");
    assert_eq!(
        title.parsed().to_lossy_string(),
        "First line\nSecond line\nThird line"
    );
}

#[test]
fn library_expansion_defaults_remain_behavior_preserving() {
    let library = Library::parse(
        r#"
@string{venue = "VLDB"}
@article{paper, title = venue, month = jan}
"#,
    )
    .unwrap();
    let entry = &library.entries()[0];

    assert_eq!(entry.get_as_string("title"), Some("VLDB".to_string()));
    assert_eq!(entry.get_as_string("month"), Some("January".to_string()));
}

#[cfg(feature = "latex_to_unicode")]
#[test]
fn value_text_can_be_latex_to_unicode_normalized() {
    let value = Value::Literal(Cow::Borrowed(r#"Jos\'e"#));
    assert_eq!(value.to_unicode_plain_string(), "José");
}