Skip to content

citation

BibTypeRule dataclass

BibTypeRule(entry_type: str, standard_name: str = 'default', required_fields: List[str] = list(), suggested_fields: List[str] = list(), optional_fields: List[str] = list(), forbidden_fields: List[str] = list(), field_mappings: Dict[str, str] = dict(), formatting: FormattingRules = FormattingRules(), output: OutputRules = OutputRules(), version: str = '1.0')

Rules for a specific BibTeX entry type.

to_dict

to_dict() -> Dict[str, Any]

Convert to dictionary for serialization.

Source code in ures/literature/citation/rules/data_type.py
63
64
65
def to_dict(self) -> Dict[str, Any]:
    """Convert to dictionary for serialization."""
    return asdict(self)

FormattingRules dataclass

FormattingRules(year_range: tuple[int, int] = (1000, 2100), page_separator: str = '--', proceedings_style: str = 'full')

Formatting rules for bibliography entries.

OutputRules dataclass

OutputRules(field_order: List[str] = list(), conditional_fields: Dict[str, str] = dict(), required_middlewares: List[Type[BlockMiddleware]] = list(), max_authors: int = 5)

Output formatting rules.

CitationInfo dataclass

CitationInfo(key: str, sources: List[CitationSource] = list(), bibliography: Optional[Entry] = None)

The data structure to hold citation information.

OutputOnlyDesiredFieldsMiddleware

OutputOnlyDesiredFieldsMiddleware(rule_register: Optional[BibRuleRegister] = None)

Bases: CitationMiddleware

Keep only desired fields in the output.

Source code in ures/literature/citation/middlewares.py
329
330
331
def __init__(self, rule_register: Optional[BibRuleRegister] = None):
    super().__init__()
    self.rule_register = rule_register or BibRuleRegister()

OutputCleanupNoneResultMiddleware

OutputCleanupNoneResultMiddleware(rule_register: Optional[BibRuleRegister] = None)

Bases: CitationMiddleware

Cleanup entries that are invalid or have missing required fields.

Source code in ures/literature/citation/middlewares.py
30
31
32
def __init__(self, rule_register: Optional[BibRuleRegister] = None):
    super().__init__()
    self.rule_register = rule_register or BibRuleRegister()

OutputLimitMaxAuthors

OutputLimitMaxAuthors(rule_register: Optional[BibRuleRegister] = None)

Bases: CitationMiddleware

Keep only desired fields in the output.

Source code in ures/literature/citation/middlewares.py
362
363
364
def __init__(self, rule_register: Optional[BibRuleRegister] = None):
    super().__init__()
    self.rule_register = rule_register or BibRuleRegister()

TypeNormalizationMiddleware

TypeNormalizationMiddleware(rule_register: Optional[BibRuleRegister] = None)

Bases: CitationMiddleware

Normalize entry types (conference -> inproceedings, etc.)

Source code in ures/literature/citation/middlewares.py
30
31
32
def __init__(self, rule_register: Optional[BibRuleRegister] = None):
    super().__init__()
    self.rule_register = rule_register or BibRuleRegister()

RuleBasedValidationMiddleware

RuleBasedValidationMiddleware(rule_register: Optional[BibRuleRegister] = None)

Bases: CitationMiddleware

Validate entries against predefined rules.

Source code in ures/literature/citation/middlewares.py
264
265
266
def __init__(self, rule_register: Optional[BibRuleRegister] = None):
    super().__init__()
    self.rule_register = rule_register or BibRuleRegister()

transform_entry

transform_entry(entry: Entry, *args, **kwargs)

Validate entry using dataclass rules.

Source code in ures/literature/citation/middlewares.py
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
def transform_entry(self, entry: Entry, *args, **kwargs):
    """Validate entry using dataclass rules."""
    is_valid = True
    rule = self.rule_register.get_rule(entry.entry_type)
    missing_required = []
    for req_field in rule.required_fields:
        if not field_group_present(entry, req_field):
            missing_required.append(req_field)

    if len(missing_required) > 0:
        is_valid = False

    suggested_fields = getattr(rule, "suggested_fields", [])
    if not isinstance(suggested_fields, list):
        suggested_fields = []
    missing_suggested = []
    for spec in suggested_fields:
        if not field_group_present(entry, spec):
            missing_suggested.append(spec)

    is_valid_field = Field(key="is_valid", value=is_valid)
    missing_fields = Field(key="missing_fields", value=missing_required)
    missing_suggested_field = Field(
        key="missing_suggested", value=missing_suggested
    )
    entry.set_field(is_valid_field)
    entry.set_field(missing_fields)
    entry.set_field(missing_suggested_field)

    return entry

AcmConferenceVenueMiddleware

AcmConferenceVenueMiddleware(rule_register: Optional[BibRuleRegister] = None)

Bases: CitationMiddleware

Put the conference city on the field ACM actually prints.

ACM-Reference-Format appends location or city to the booktitle and prints address after the publisher. Zotero's BibTeX export has only address for Place, and that Place is the venue city. BibLaTeX venue is the event location. For ACM and the default style, move the venue city onto location when location and city are empty. A publisher address is left in place when venue already holds the city. Books and IEEE entries are unchanged.

Source code in ures/literature/citation/middlewares.py
30
31
32
def __init__(self, rule_register: Optional[BibRuleRegister] = None):
    super().__init__()
    self.rule_register = rule_register or BibRuleRegister()

FieldNormalizationMiddleware

FieldNormalizationMiddleware(rule_register: Optional[BibRuleRegister] = None)

Bases: CitationMiddleware

Normalize field names (journaltitle -> journal, etc.)

Source code in ures/literature/citation/middlewares.py
30
31
32
def __init__(self, rule_register: Optional[BibRuleRegister] = None):
    super().__init__()
    self.rule_register = rule_register or BibRuleRegister()

transform_entry

transform_entry(entry: Entry, *args, **kwargs) -> Entry

Transform entry fields.

Source code in ures/literature/citation/middlewares.py
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
def transform_entry(self, entry: Entry, *args, **kwargs) -> Entry:
    """Transform entry fields."""
    for field in entry.fields:
        # Handle special cases
        key = field.key
        value = field.value
        if key == "pages":
            # Normalize page ranges
            field.value = self._normalize_pages(value)
        else:
            field.value = value
        # Apply field mapping
        rules = self.rule_register.get_rule(entry.entry_type)
        field_mappings = copy.deepcopy(
            self.rule_register.get_default_field_mapping()
        )
        field_mappings.update(rules.field_mappings)
        new_key = field_mappings.get(field.key, field.key)
        field.key = new_key
    return entry

LanguageAsciiNormalizationMiddleware

LanguageAsciiNormalizationMiddleware(rule_register: Optional[BibRuleRegister] = None)

Bases: CitationMiddleware

Source code in ures/literature/citation/middlewares.py
30
31
32
def __init__(self, rule_register: Optional[BibRuleRegister] = None):
    super().__init__()
    self.rule_register = rule_register or BibRuleRegister()

normalize_language

normalize_language(language_str: str) -> Any

Normalize language string to ISO 639-1 code.

Source code in ures/literature/citation/middlewares.py
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
def normalize_language(self, language_str: str) -> Any:
    """Normalize language string to ISO 639-1 code."""
    special_cases = {
        "pinyin": "zh",
    }
    if language_str in special_cases:
        return special_cases[language_str]
    if not isinstance(language_str, str):
        logger.warning(f"Failed to normalize language '{language_str}'")
        return language_str
    language_str = language_str.strip().lower()
    try:
        lang = pycountry.languages.get(name=language_str)
        if lang and hasattr(lang, "alpha_2"):
            return lang.alpha_2
        # Try searching by common name
        for lang in pycountry.languages:
            if language_str in str(lang.name).lower():
                if hasattr(lang, "alpha_2"):
                    return lang.alpha_2
    except:
        logger.warning(f"Failed to normalize language '{language_str}'")
        return language_str
    return language_str

ProceedingsNormalizationMiddleware

ProceedingsNormalizationMiddleware(rule_register: Optional[BibRuleRegister] = None)

Bases: CitationMiddleware

Source code in ures/literature/citation/middlewares.py
30
31
32
def __init__(self, rule_register: Optional[BibRuleRegister] = None):
    super().__init__()
    self.rule_register = rule_register or BibRuleRegister()

normailize_proceedings

normailize_proceedings(proceedings_str: str) -> str

Normalize proceedings string to standard format.

Source code in ures/literature/citation/middlewares.py
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
def normailize_proceedings(self, proceedings_str: str) -> str:
    """Normalize proceedings string to standard format."""
    patterns = [
        r"\bIn\s+Proceedings\s+of\s+the\s+",
        r"\bIn\s+Proceedings\s+of\s+",
        r"\bIn\s+Proc\.\s+of\s+the\s+",
        r"\bIn\s+Proc\.\s+of\s+",
        r"\bProceedings\s+of\s+the\s+",
        r"\bProceedings\s+of\s+",
        r"\bProc\.\s+of\s+the\s+",
        r"\bProc\.\s+of\s+",
        r"\bIn\s+(?=\d|\w+\s+(International|Annual|ACM|IEEE))",
        r"\bProc\.\s+",
    ]
    proceedings_str = self._shorten_venue_year(proceedings_str.strip())
    for pattern in patterns:
        if re.search(pattern, proceedings_str, re.IGNORECASE):
            proceedings_str = re.sub(
                pattern, "", proceedings_str, flags=re.IGNORECASE
            )
            proceedings_str = re.sub(r"\s+", " ", proceedings_str).strip()
            break
    prefix_map = {
        "remove": "",
        "full": "In Proceedings of the ",
        "short": "In Proc. of the ",
        "proceedings": "Proceedings of the ",
        "minimal": "In ",
        "proc": "Proc. ",
    }

    style = self.rule_register.get_rule(
        "inproceedings"
    ).formatting.proceedings_style
    prefix = prefix_map.get(style, "full")
    return f"{prefix}{proceedings_str}".strip()

CitationManager

CitationManager(bibliography_files: Optional[Union[Union[str, Path], List[Union[str, Path]]]] = None, bibliography_style: str = 'default')

Load bibliography files and import citations from TeX or BBL sources.

Examples:

>>> from ures.literature import CitationManager
>>> manager = CitationManager(bibliography_style="acm")
>>> manager.citations
[]
Source code in ures/literature/citation/__init__.py
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
def __init__(
    self,
    bibliography_files: Optional[
        Union[Union[str, Path], List[Union[str, Path]]]
    ] = None,
    bibliography_style: str = "default",
):
    # Load bibliography files
    if bibliography_files is None:
        bibliography_files = []
    if not isinstance(bibliography_files, list):
        bibliography_files = [bibliography_files]
    self._bib_manager = BibManager(bibliography_style=bibliography_style)
    for bib_file in bibliography_files:
        self._bib_manager.append_bibliography(bib_file)
    # Store citation items for quick access
    self._citations: list[CitationInfo] = []

import_citations

import_citations(files: List[Union[str, Path]], cleanup: bool = False) -> dict[str, CitationInfo]

Import citations from the given files.

Parameters:

  • files (List[Union[str, Path]]) –

    List of file paths to import citations from.

  • cleanup (bool, default: False ) –

    Whether to clean up the citations previously stored. Defaults to False.

Returns:

Source code in ures/literature/citation/__init__.py
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
def import_citations(
    self, files: List[Union[str, Path]], cleanup: bool = False
) -> dict[str, CitationInfo]:
    """Import citations from the given files.

    Args:
                    files (List[Union[str, Path]]): List of file paths to import citations from.
                    cleanup (bool): Whether to clean up the citations previously stored. Defaults to False.

    Returns:
                    List[CitationInfo]: List of imported citations.

    """
    unique_citations = {}
    for file in files:
        file_path = Path(file)
        if not file_path.exists():
            continue

        if file_path.suffix == ".tex":
            extractor = TexCitationExtractor()
        elif file_path.suffix == ".bbl":
            extractor = BBLCitationExtractor()
        else:
            logger.warning(
                f"Unsupported file type: {file_path.suffix} for file {file_path}. Skipping."
            )
            continue

        for cite in extractor.extract_citations(file_path):
            if cite.key not in unique_citations:
                unique_citations[cite.key] = cite
            else:
                unique_citations[cite.key].sources.extend(cite.sources)

    # Merge citation information with existing citations
    if cleanup:
        self._citations.clear()
        self._add_citation(list(unique_citations.values()))
    else:
        for key, cite in unique_citations.items():
            is_found = False
            for stored_cite in self._citations:
                if stored_cite.key == key:
                    stored_cite.sources.extend(cite.sources)
                    is_found = True
                    break
            if not is_found:
                self._add_citation(cite)

    return unique_citations

display_invalid_citations

display_invalid_citations() -> None

Display citations that do not have corresponding bibliography entries.

Source code in ures/literature/citation/__init__.py
143
144
145
146
147
148
149
150
151
152
def display_invalid_citations(self) -> None:
    """Display citations that do not have corresponding bibliography entries."""
    for cite in self._citations:
        if (
            cite.bibliography is not None
            and cite.bibliography.get("is_valid", False) is False
        ):
            print(
                f"Citation Key: {cite.key} is invalid. Missing Fields: {cite.bibliography.get('missing_fields', [])}"
            )

to_library

to_library() -> bibtexparser.Library

Convert all citations to a bibtexparser Library.

Source code in ures/literature/citation/__init__.py
154
155
156
157
158
159
160
161
162
def to_library(self) -> bibtexparser.Library:
    """Convert all citations to a bibtexparser Library."""
    lib = bibtexparser.Library()
    if len(self.manager.bibliograph_library.strings) > 0:
        lib.add(self.manager.bibliograph_library.strings)
    for cite in self._citations:
        if cite.bibliography is not None:
            lib.add(copy.deepcopy(cite.bibliography))
    return lib

save_bibliography

save_bibliography(file_path: str, middlewares: Optional[List[Union[Type[BlockMiddleware], BlockMiddleware]]] = None) -> None

Save all bibliography entries to a BibTeX file.

Parameters:

  • file_path (Union[str, Path]) –

    Path to save the BibTeX file.

  • middlewares (Optional[List[Type[CitationMiddleware]]], default: None ) –

    List of middleware classes to process the entries before saving. If None, no additional middleware will be applied. Defaults to None.

Source code in ures/literature/citation/__init__.py
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
def save_bibliography(
    self,
    file_path: str,
    middlewares: Optional[
        List[
            Union[
                Type[bibtexparser.middlewares.BlockMiddleware],
                bibtexparser.middlewares.BlockMiddleware,
            ]
        ]
    ] = None,
) -> None:
    """Save all bibliography entries to a BibTeX file.

    Args:
        file_path (Union[str, Path]): Path to save the BibTeX file.
        middlewares (Optional[List[Type[CitationMiddleware]]]): List of middleware classes to process the entries before saving.
            If None, no additional middleware will be applied. Defaults to None.
    """
    self._bib_manager.export_to_file(
        file_path=file_path, library=self.to_library(), middlewares=middlewares
    )