Validate🔗
Validation is always explicit — loading and saving never validate implicitly.
import sil_lift
# Exhaustive: a lazy stream of Problems (schema + semantic layers).
for problem in sil_lift.iter_problems("dictionary.lift"):
print(problem)
# error [dangling-ref] dictionary.lift:88 (entry apu): ref 'nope' matches ...
# Fail-fast: raises LiftValidationError on the first error-level problem.
sil_lift.validate_file("dictionary.lift")
# In-memory state (serializes first — a documented cost on large lexicons):
lex = sil_lift.load("dictionary.lift")
problems = list(lex.iter_problems())
Each Problem carries level ("error"/"warning"), a stable code, message, and as much of an address as the finding has: file (None when the lexicon has no path), entry_id when it concerns one entry, guid when the object it concerns has one (an entry, or a range-element), and line when it maps to a line in the document. A finding about a range is addressed to the .lift-ranges companion that defines it, and carries no entry. Unset fields are None — null in --format json, where every key is always present.
The layers🔗
- RELAX NG against the LIFT 0.13 grammar (vendored from lift-standard — a byte-identical copy committed into this package).
- Ranges schema — this project's
lift-ranges-0.13.rng— over every tracked.lift-rangescompanion, addressed to the companion rather than the.lift. - Semantic checks the grammar cannot express — ten of them, one code each.
Problem codes🔗
Every finding carries one of these, whichever layer produced it — schema and uri-not-rfc come from the schema layers, the other ten are semantic checks. The strings are a supported interface; --strict promotes every warning to an error.
| code | level | what it flags |
|---|---|---|
ambiguous-ranges-file |
warning | several files answering to one companion name under case folding and NFC |
dangling-ranges-href |
warning | a header range/@href resolving to no companion file |
dangling-ref |
error | a relation/@ref or variant/@ref matching no entry or sense |
duplicate-form-lang |
warning | two forms in one multitext sharing a language |
duplicate-guid |
error | a guid reused among entries, or among one document's ranges/range-elements |
missing-id |
error | opt-in via require_ids: an entry without a guid, a sense without an id |
missing-media |
warning | a referenced audio or picture file not on disk |
normalization-mismatch |
warning | a name that reaches the id it refers to only under NFC |
range-parent |
error | a range-element/@parent no sibling id defines |
schema |
error | a RELAX NG grammar violation, in the .lift or in a companion |
undefined-range-value |
warning | a grammatical-info or range-keyed trait value the range does not list |
uri-not-rfc |
warning | an href that is not a valid URI — FLEx's file://C:/... |
All three layers work from what save() would write, so a document that cannot be serialized at all is reported as a single lone-surrogate error instead — see Fidelity guarantees.
A companion name matching several files loads none of them: the ranges they define go absent until all but one is renamed or removed.
Real-world FieldWorks (FLEx) output🔗
FieldWorks systematically writes some content that strict tooling rejects. Here is sil-lift's policy, so that real lexicons validate usefully:
file://C:/...hrefs (invalid URIs) are reported as warnings (uri-not-rfc), not schema errors — the C# validator never rejected them.- Legally interleaved children (e.g.
field, note, field, notein a sense) are not flagged, working around a false positive in libxml2. - FLEx's
trait/fieldextensions insiderange-elementare reported (schema errors against the ranges schema): they are genuine spec deviations. - Names are resolved against range and range-element
ids under Unicode NFC normalization —parentlinks, range values, and thetraitname or headerrangeid that keys a range. FLEx normalizes to NFC on export, but some writes used to bypass that step, so a range-elementidcan be NFD while its labels, its ownparent, and the.liftvalues naming it are NFC.- Compared exactly, a sound export looks broken — and a range whose
idis spelled the other way goes unchecked entirely, since a trait name that reaches no range is silently accepted. - A name that matched only after normalizing is reported as a
normalization-mismatchwarning, once per id however many references differ, addressed to the file that defines it. The data is sound, but a consumer comparing raw strings will not resolve those references. - The ids are never rewritten: the file keeps the spellings it came with.
- Compared exactly, a sound export looks broken — and a range whose