Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 29 additions & 3 deletions flexlibs2/code/Lexicon/AllomorphOperations.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,9 +202,11 @@ def Create(self, entry_or_hvo, form, morphType=None, wsHandle=None):
Args:
entry_or_hvo: The ILexEntry object or HVO.
form (str): The allomorph form (e.g., "-ing", "walk", "pre-").
morphType (IMoMorphType, optional): The morpheme type object.
If None (default), inherits from the entry's LexemeFormOA morph type,
matching FLEx GUI behavior.
morphType (IMoMorphType | str | None): The morpheme type. Pass an
IMoMorphType object, a name string (e.g. 'suffix', '=enclitic',
'-prefix'), or None to inherit from the entry's LexemeFormOA
morph type (matching FLEx GUI behaviour). Display markers are
stripped automatically so UI-copied names resolve correctly.
wsHandle: Optional writing system handle. Defaults to vernacular WS.

Returns:
Expand Down Expand Up @@ -252,6 +254,30 @@ def Create(self, entry_or_hvo, form, morphType=None, wsHandle=None):
entry = self.__GetEntryObject(entry_or_hvo)
wsHandle = self.__WSHandle(wsHandle)

# Accept morph type as a string name (with or without display markers).
if isinstance(morphType, str):
bare = morphType.strip('-=~<>')
morph_types = self.project.lp.LexDbOA.MorphTypesOA
from ..Shared.string_utils import normalize_match_key, best_analysis_text
target = normalize_match_key(bare, casefold=True)
def _find(possibilities):
for mt in possibilities:
mt_name = best_analysis_text(mt.Name)
if mt_name and normalize_match_key(mt_name, casefold=True) == target:
return mt
if mt.SubPossibilitiesOS.Count > 0:
found = _find(mt.SubPossibilitiesOS)
if found:
return found
return None
resolved = _find(morph_types.PossibilitiesOS) if morph_types else None
if resolved is None:
raise FP_ParameterError(
f"Morph type '{morphType}' not found. "
"Use project.LexEntry.GetAvailableMorphTypes() to see valid names."
)
morphType = resolved

# If no morphType provided, inherit from entry's LexemeFormOA (FLEx behavior)
if morphType is None:
if not entry.LexemeFormOA or not entry.LexemeFormOA.MorphTypeRA:
Expand Down
83 changes: 81 additions & 2 deletions flexlibs2/code/Lexicon/LexEntryOperations.py
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,8 @@ def GetLexemeForm(self, entry_or_hvo, wsHandle=None):

Raises:
FP_NullParameterError: If entry_or_hvo is None
FP_WritingSystemError: If wsHandle refers to a writing system not
configured in the project.

Example:
>>> entry = project.LexEntry.Find("run")
Expand Down Expand Up @@ -876,6 +878,8 @@ def GetCitationForm(self, entry_or_hvo, wsHandle=None):

Raises:
FP_NullParameterError: If entry_or_hvo is None
FP_WritingSystemError: If wsHandle refers to a writing system not
configured in the project.

Example:
>>> entry = project.LexEntry.Find("run")
Expand Down Expand Up @@ -1514,6 +1518,74 @@ def ValidateMorphType(self, morph_type_name):

return (False, None, None)

@OperationsMethod
def GetAllByMorphType(self, name_or_list, match_allomorphs=False):
"""
Return all entries whose lexeme-form morph type matches the given name(s).

Comparison is by GUID (not substring), so affix-family names like
'suffix' never accidentally match 'circumfix-suffix' or similar.
Display markers are stripped before lookup, so '=enclitic' resolves the
same as 'enclitic'.

Args:
name_or_list (str | list[str]): Morph type name(s) to match. A
single string matches one type; a list uses OR semantics (useful
for affix-family grouping such as
['prefix', 'suffix', 'infix']).
match_allomorphs (bool): When False (default) only the lexeme-form
morph type is checked. When True, entries are also included if
any AlternateFormsOS allomorph matches.

Returns:
list: ILexEntry objects whose morph type matches.

Raises:
FP_ParameterError: If any name in name_or_list is not found in the
project's morph type list.

Example:
>>> affixes = project.LexEntry.GetAllByMorphType(['prefix', 'suffix', 'infix'])
>>> stems = project.LexEntry.GetAllByMorphType('stem')
>>> enclitics = project.LexEntry.GetAllByMorphType('=enclitic') # markers OK

Notes:
- An entry's morph type is on its lexeme form; allomorph morph types
may differ and are only checked when match_allomorphs=True.
- List input uses OR semantics; chain set() operations for AND/exclusion.

See Also:
ValidateMorphType, GetMorphType, GetAvailableMorphTypes
"""
if isinstance(name_or_list, str):
name_or_list = [name_or_list]

# Resolve all names to morph-type GUIDs up front so errors surface early.
target_guids = set()
for name in name_or_list:
mt = self.__FindMorphType(name)
if mt is None:
raise FP_ParameterError(
f"Morph type '{name}' not found. "
"Use GetAvailableMorphTypes() to see valid names."
)
target_guids.add(mt.Guid)

results = []
for entry in self.project.lp.LexDbOA.Entries:
matched = False
if entry.LexemeFormOA and entry.LexemeFormOA.MorphTypeRA:
if entry.LexemeFormOA.MorphTypeRA.Guid in target_guids:
matched = True
if not matched and match_allomorphs:
for alm in entry.AlternateFormsOS:
if alm.MorphTypeRA and alm.MorphTypeRA.Guid in target_guids:
matched = True
break
if matched:
results.append(entry)
return results

# --- Sense Management ---

@OperationsMethod
Expand Down Expand Up @@ -2978,13 +3050,20 @@ def __FindMorphType(self, name):
"""
Find a morph type by name (case-insensitive).

Display markers (leading/trailing -, =, ~, <, >) are stripped before
matching so that UI-copied strings like '=enclitic' or '-suffix' resolve
to the same canonical type as 'enclitic' or 'suffix'.

Args:
name (str): The morph type name to search for
name (str): The morph type name to search for (bare or with display markers)

Returns:
IMoMorphType or None: The morph type object if found, None otherwise
"""
target = normalize_match_key(name, casefold=True)
# Strip display markers (prefix '-', '=', '~'; postfix '-', '=', '~', '<', '>')
# that FLEx shows in the UI but that are NOT part of the canonical IMoMorphType.Name.
bare = name.strip('-=~<>')
target = normalize_match_key(bare, casefold=True)
wsHandle = self.project.project.DefaultAnalWs

morph_types = self.project.lp.LexDbOA.MorphTypesOA
Expand Down
5 changes: 5 additions & 0 deletions flexlibs2/code/Lexicon/LexSenseOperations.py
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,9 @@ def GetGloss(self, sense_or_hvo, wsHandle=None):

Raises:
FP_NullParameterError: If sense_or_hvo is None.
FP_WritingSystemError: If wsHandle refers to a writing system not
configured in the project. Callers iterating over writing
systems should catch this or verify with project.WSHandle first.

Example:
>>> entry = list(project.LexiconAllEntries())[0]
Expand Down Expand Up @@ -884,6 +887,8 @@ def GetDefinition(self, sense_or_hvo, wsHandle=None):

Raises:
FP_NullParameterError: If sense_or_hvo is None.
FP_WritingSystemError: If wsHandle refers to a writing system not
configured in the project.

Example:
>>> entry = list(project.LexiconAllEntries())[0]
Expand Down
Loading