Worked example: bulk-editing glosses🔗
A common maintenance task: normalize spelling across every English gloss in a lexicon (British → American, or vice versa) without disturbing anything else in the file. This walks through one script that loads, edits, validates, and saves — showing the editing API and the fidelity guarantee working together.
The script🔗
import sys
import sil_lift
path = "dictionary.lift"
lex = sil_lift.load(path)
edited_glosses = 0
for entry in lex.entries:
for sense in entry.all_senses():
for gloss in sense.glosses:
if gloss.lang != "en":
continue
old = str(gloss.text)
new = old.replace("colour", "color")
if new != old:
gloss.text = sil_lift.Text([new])
edited_glosses += 1
changed = lex.changed_entries()
errors = [p for p in lex.iter_problems() if p.level == "error"]
if errors:
for problem in errors:
print(problem)
sys.exit(f"aborting: {len(errors)} validation error(s), nothing saved")
lex.save()
print(f"edited {edited_glosses} gloss(es) across {len(changed)} entry(ies)")
A few things worth noting:
entry.all_senses()yields every sense and subsense, depth-first in document order.entry.sensesholds only the top level, so a bulk edit that walked it would silently skip any gloss nested under a subsense.
gloss.textis aText, not a plain string:str(gloss.text)flattens it for matching, and the replacement is written back withsil_lift.Text([new])rather than mutating the string in place.lex.changed_entries()reports which entries differ from the file as loaded. Since an entry's digest covers its whole subtree, an edit to a nested subsense reports the entry that contains it, while assigning a field the value it already had isn't reported.changed_entries()covers entry content only, so it is a count, not a write guard. To skip a save that would change nothing, uselex.changes(); see Saving.- Validating in memory (
lex.iter_problems()) serializes the edited state first, so it correctly reflects the edit before anything is written to disk. Aborting on any"error"-levelProblem— warnings are left for the caller to decide about — means a bad edit never reachessave(). lex.save()stamps a freshdateModifiedon exactly the entries this script changed, fillingdateCreatedwhere it was blank. Without it a lexicon this script has rewritten would look untouched to FieldWorks or to The Combine's LIFT import, both of which decide what to update from that attribute — and it stamps<entry>only, however deep the edit went: the entry containing the edited subsense gloss, not the sense.
Glosses aren't the only thing worth touching this way. The same Multitext mapping surface applies to definitions and every other multilingual field on an entry or sense:
sense.definition["en"] = "the color of a thing"
Running it🔗
Run against a small lexicon with a gloss and a subsense gloss that both say "colour":
edited 2 gloss(es) across 1 entry(ies)
The fidelity payoff🔗
The guarantee is per entry: an entry whose model didn't change comes back out byte-identical to how it was read in, and only the entries you actually touched are re-serialized. In the run above, one entry had glosses edited — every other entry in the file kept its exact bytes. (Note the granularity: editing any part of an entry re-serializes that whole entry, including its untouched sibling senses.) Editing one gloss in a 50,000-entry lexicon therefore produces a diff touching one entry, not a reformatted file. See Fidelity guarantees for the precise contract.