Source code for conllu_tools.utils.xpos.llct_converters

"""Functions for converting between LLCT and Perseus XPOS tags."""

from __future__ import annotations

from conllu_tools.constants import LLCT_CONCORDANCES
from conllu_tools.utils.features import feature_string_to_dict
from conllu_tools.utils.upos import upos_to_perseus


def _validate_xpos_value(value: str | None, feats_type: str) -> str | None:
    """Validate LLCT XPOS value against concordance."""
    if value is None or value == '-':
        return None

    valid_values = LLCT_CONCORDANCES[feats_type].values()

    if value in valid_values:
        return value

    return None


def _reconcile_xpos_feats(xpos_value: str | None, feats_value: str | None, feats_type: str) -> str:
    """Reconcile LLCT XPOS and FEATS values."""
    # map FEATS value to XPOS value
    feats_value = LLCT_CONCORDANCES[feats_type].get(feats_value) if feats_value else None
    # validate both values
    xpos_value = _validate_xpos_value(xpos_value, feats_type)
    feats_value = _validate_xpos_value(feats_value, feats_type)

    # if neither value is valid, return '-'
    if not any([xpos_value, feats_value]):
        return '-'

    # if only one value is valid, return that value
    if xpos_value and not feats_value:
        return xpos_value
    if feats_value and not xpos_value:
        return feats_value

    # prefer XPOS value if both are valid
    return xpos_value if xpos_value else '-'


[docs] def llct_to_perseus(upos: str, xpos: str, feats: dict[str, str] | str) -> str: """Convert LLCT UPOS, XPOS, and FEATS to Perseus XPOS tag. Arguments: upos: The Universal Part of Speech tag. xpos: An LLCT XPOS string. feats: A feature string or dictionary of features. Returns: A Perseus XPOS string. """ if not feats and '|' not in xpos: # invalid punctuation xpos return f'{upos_to_perseus(upos)}--------' if isinstance(feats, str): feats = feature_string_to_dict(feats) parts = xpos.split('|') if len(parts) != 9: # noqa: PLR2004 # complete with dash if necessary parts += ['-'] * (9 - len(parts)) parts.pop(1) # drop second part (repeat upos?) pos, person, number, tense, mood, voice, gender, case, degree = parts # ensure correct PoS tag pos = upos_to_perseus(upos) # 1: part of speech person = feats.get('Person', '-') # 2: person number = _reconcile_xpos_feats(number, feats.get('Number'), 'number') # 3: number tense = _reconcile_xpos_feats(tense, feats.get('Tense'), 'tense') # 4: tense mood = _reconcile_xpos_feats(mood, feats.get('Mood'), 'mood') # 5: mood voice = _reconcile_xpos_feats(voice, feats.get('Voice'), 'voice') # 6: voice gender = _reconcile_xpos_feats(gender, feats.get('Gender'), 'gender') # 7: gender case = _reconcile_xpos_feats(case, feats.get('Case'), 'case') # 8: case degree = _reconcile_xpos_feats(degree, feats.get('Degree'), 'degree') # 9: degree return f'{pos}{person}{number}{tense}{mood}{voice}{gender}{case}{degree}'