Usage

File and string APIs

File APIs accept a path as str or pathlib.Path. They infer the format from a recognized suffix, unless format is supplied explicitly. Reads use UTF-8 and missing files raise FileNotFoundError.

from pathlib import Path

 from bibliography_io import load_raw, write

 records = load_raw(Path("references.bib"))
 write(records, path="references.ris")

String APIs require an explicit format:

from bibliography_io import to_string
from bibliography_io import loads_raw

records = loads_raw("@article{example, title={An example}}", format="bib")
bibtex = to_string(records, format="bib")

Supported formats

load_raw reads BibTeX (.bib), RIS (.ris), CSV (.csv), Excel (.xls and .xlsx), ENL/EndNote (.enl and .txt), NBIB (.nbib), and JSON (.json). It also accepts Markdown (.md) for reference-list parsing only when the optional backend is installed.

write writes BibTeX, RIS, CSV, Excel (.xlsx), and Markdown tables. JSON, ENL, NBIB, and legacy Excel (.xls) are read-only. to_string supports BibTeX, RIS, CSV, and Markdown-table output. Excel is a file-only format; loads_raw supports the string-based input formats.

Use write(..., primary=True) to write a primary-record mapping. This mode validates that records have unique, non-empty IDs and valid entry types, and only permits BibTeX output.

Serialization sorts records by their record IDs by default for both list and mapping inputs. Set sort_by_id=False on write(), to_string(), or write_string() to preserve list order or mapping iteration order. The primary=True mode of write() continues to sort primary records by ID.

Format aliases include bibtex for BibTeX, excel for Excel, endnote for ENL, and markdown for Markdown. File suffixes are case-insensitive.

Markdown forms and optional dependencies

Markdown table output is built into bibliography-io and does not need an optional backend. Markdown reference-list parsing uses CoLRev and GROBID and is enabled by installing bibliography-io[markdown]. A running GROBID service is also required. Without the extra, a Markdown read raises OptionalBackendUnavailableError. Excel loading and writing require bibliography-io[excel]; the all extra installs both optional feature sets.

Record shape

load_raw() and loads_raw() return records in source order as a list of dictionaries. They preserve parser-level field names and value types: values may be strings, lists for repeated fields, mappings, or other format-native values. Raw loading does not require or generate record IDs, and it does not apply source-specific normalization. Unknown fields are retained.

load() returns records in source order as a list of dictionaries with normalized field names and string values. This applies both to generic normalization and to results returned by source-specific normalization hooks. Repeated values are combined into strings (authors use `` and ; keywords use ``; ``; other conflicting values use ``; ``). Empty scalar values and empty items in repeated values are omitted. A source-specific hook may select and normalize fields, but its non-string field values are converted to strings before ``load() returns. Supply a source identifier and optional source_version to use source-specific preprocessing and normalization:

from bibliography_io import load

records = load(
    "references.ris",
    source="colrev.crossref",
    source_version="2026",
)

Without source, load() applies bibliography-io’s generic normalization. When a source is selected, source names the CoLRev package by its unique endpoint identifier, such as colrev.crossref. The format is inferred from the file suffix unless format is supplied; use format if the suffix is ambiguous or does not match the file’s actual format. See Source extensions for bibliography-io for package discovery and preprocessing/normalization hook details.

DataFrame helpers

load_df() returns the normalized records from load() as DataFrame rows in source order, with a default RangeIndex. It accepts source, source_version, and format. load_primary_df() returns primary records with IDs as the DataFrame index. Parser-level records remain available as lists through load_raw() and loads_raw(). Pandas is optional; install the Excel extra with pip install bibliography-io[excel] to use these helpers.

get_nr_records() returns the number of raw records in a file.

from bibliography_io import load_df
from bibliography_io import load_primary_df

normalized = load_df("references.ris")
primary = load_primary_df("references.bib")

Raw records have not been curated as primary records. Normalization belongs to load() and is generic unless a source is selected. Formats retain their own limits on what parser-level information can be represented. Unlike load(), load_primary() and loads_primary() retain mapping and list values only for BibTeX fields explicitly declared with mapping_fields and list_fields; all other field values are strings. The declarations define how those BibTeX fields are parsed, and do not change the behavior of load().

Format examples

String loaders require an explicit format. Use loads_raw() to inspect format-specific field names and parser-level values; use loads() for normalized field names and values. In normalized records, authors are combined into one author string joined by the word and.

from bibliography_io import loads, loads_raw

bib = """\
@article{g2021,
  title  = {Trust},
  author = {Guo, Wenbo and Straub, Detmar},
}
"""
loads_raw(bib, format="bib")
# [
#     {
#         'ID': 'g2021',
#         'ENTRYTYPE': 'article',
#         'title': 'Trust',
#         'author': 'Guo, Wenbo and Straub, Detmar',
#     }
# ]

ris = """\
TY  - JOUR
TI  - Trust
AU  - Guo, Wenbo
AU  - Straub, Detmar
ER  -
"""
loads_raw(ris, format="ris")
# [
#     {
#         'TY': 'JOUR',
#         'TI': 'Trust',
#         'AU': ['Guo, Wenbo', 'Straub, Detmar'],
#     }
# ]
loads(ris, format="ris")
# [
#     {
#         'ENTRYTYPE': 'article',
#         'title': 'Trust',
#         'author': 'Guo, Wenbo and Straub, Detmar',
#     }
# ]

json_text = """[
  {
    "title": "Trust",
    "authors": ["Guo, Wenbo", "Straub, Detmar"],
    "metadata": {"rank": 1}
  }
]"""
loads_raw(json_text, format="json")
# [
#     {
#         'title': 'Trust',
#         'authors': ['Guo, Wenbo', 'Straub, Detmar'],
#         'metadata': {'rank': 1},
#     }
# ]
loads(json_text, format="json")
# [
#     {
#         'title': 'Trust',
#         'author': 'Guo, Wenbo and Straub, Detmar',
#         'metadata': "{'rank': 1}",
#         'ENTRYTYPE': 'misc',
#     }
# ]

Structured BibTeX fields

Provenance mappings and lists can be written to BibTeX when callers explicitly declare their field names. A mapping value is a Python dictionary from field names to {"source": ..., "note": ...} dictionaries. Nested arbitrary mappings are not supported. List values are Python lists of strings. Each provenance or list item is serialized in the legacy semicolon-delimited form.

from bibliography_io import to_string
from bibliography_io import loads_raw

provenance = {
    "author": {"source": "ebsco_bib.bib/11111111111111111", "note": ""},
    "title": {"source": "manual.bib/entry1", "note": "checked"},
}
records = {
    "example": {
        "ENTRYTYPE": "article",
        "colrev_masterdata_provenance": provenance,
    }
}
text = to_string(
    records,
    format="bib",
    mapping_fields=["colrev_masterdata_provenance"],
)
restored = loads_raw(
    text,
    format="bib",
    mapping_fields=["colrev_masterdata_provenance"],
)

The mapping is written as entries such as author:source;;; list values are written as semicolon-terminated items. The file APIs accept the same options. Mapping and list values must follow these provenance conventions; values that cannot be represented with the delimiters raise SerializationError. Structured-field options supplied to non-BibTeX formats raise UnsupportedFormatError. See Design principles for the full format contract.

BibTeX field ordering

BibTeX output uses a CoLRev-oriented field order by default: origin, status, provenance, PDF and screening metadata, file and exclusion metadata, DOI and identifiers, then bibliographic fields. This default order is colrev_origin, colrev_status, colrev_masterdata_provenance, colrev_data_provenance, colrev_pdf_id, screening_criteria, file, prescreen_exclusion, doi, grobid-version, colrev.dblp.dblp_key, colrev.semantic_scholar.id, colrev.web_of_science.unique-id, author, booktitle, journal, title, year, volume, number, pages, editor, publisher, url, and abstract. Fields not in this list are written alphabetically after the CoLRev-ordered fields. Pass field_order to to_string() or write() to choose a different preferred order; all remaining fields are still written alphabetically. Set sort_fields=False to preserve the input order of all fields, including the predefined or explicitly ordered fields:

bibtex = to_string(
    records,
    format="bib",
    field_order=["author", "title", "year", "doi"],
)

original_order = to_string(records, format="bib", sort_fields=False)

Field names in field_order are normalized like record field names. The sort_fields option defaults to True and applies to BibTeX output; non-BibTeX writers are unaffected.

BibTeX fields use a fixed 28-character field-name column, followed by two spaces and =. Longer names extend beyond the aligned column. Fixed padding keeps the whitespace on other fields unchanged as fields are added or renamed, which helps preserve Git history diffs.