From 9a3f3919c8dfc78de353abc059e6ef4ad9fe28d8 Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Fri, 23 Oct 2026 10:49:04 -0400 Subject: [PATCH 1/4] Match LIFT list values ignoring surrounding whitespace LIFT import matched list items, such as semantic domains, only on exact text. Semantic domain lists from FieldWorks templates before 2017 have trailing spaces in some English names (e.g., "Necessary "), so a LIFT value of "9.4.2.3 Necessary" from another program created a duplicate domain instead of linking to the existing one. Also key possibility lookups on names and abbreviations trimmed of surrounding whitespace, and fall back to the trimmed value when looking up a trait. Co-Authored-By: Claude Opus 5.5 --- .../LexTextControlsTests/LiftMergerTests.cs | 41 +++++++++++++++++++ .../LexTextControls/LiftMergerRanges.cs | 4 +- .../LiftMergerSupportCodeAndClasses.cs | 26 +++++++----- 3 files changed, 59 insertions(+), 12 deletions(-) diff --git a/Src/LexText/LexTextControls/LexTextControlsTests/LiftMergerTests.cs b/Src/LexText/LexTextControls/LexTextControlsTests/LiftMergerTests.cs index 582069e9cf..6ef6bef39f 100644 --- a/Src/LexText/LexTextControls/LexTextControlsTests/LiftMergerTests.cs +++ b/Src/LexText/LexTextControls/LexTextControlsTests/LiftMergerTests.cs @@ -4803,6 +4803,47 @@ public void ImportRangeWithNoId_DoesNotDuplicateGuids_AnthroCode() Assert.That(Cache.LangProject.AnthroListOA.PossibilitiesOS, Has.Count.EqualTo(1), "Should have merged import with ICmAnthroItem from input"); } + /// + /// Semantic domain lists from FieldWorks templates before 2017 have trailing spaces + /// in some English names (e.g., "Necessary "), which other programs trim before + /// writing LIFT. + /// + [Test] + public void ImportSemanticDomainTrait_MatchesExistingDomainWithUntrimmedName() + { + SetWritingSystems("fr"); + var en = Cache.WritingSystemFactory.GetWsFromStr("en"); + var semDomList = Cache.LangProject.SemanticDomainListOA; + var semDom = Cache.ServiceLocator.GetInstance().Create(); + semDomList.PossibilitiesOS.Add(semDom); + semDom.Abbreviation.set_String(en, "9.4.2.3"); + semDom.Name.set_String(en, "Necessary "); + var semDomCount = semDomList.ReallyReallyAllPossibilities.Count; + var liftData = new[] + { + "", + "", + "", + "", + "
baba
", + "
", + "", + "", + "", + "
", + "
" + }; + + var sOrigFile = CreateInputFile(liftData); + var logFile = TryImport(sOrigFile, 1); + File.Delete(sOrigFile); + File.Delete(logFile); + + Assert.That(semDomList.ReallyReallyAllPossibilities, Has.Count.EqualTo(semDomCount), "Should not create a new semantic domain"); + var sense = Cache.ServiceLocator.GetInstance().AllInstances().Single(); + Assert.That(sense.SemanticDomainsRC.Single(), Is.SameAs(semDom)); + } + [Test] public void ImportRangeWithExistingObject_DoesNotDuplicate_UnifiesData() { diff --git a/Src/LexText/LexTextControls/LiftMergerRanges.cs b/Src/LexText/LexTextControls/LiftMergerRanges.cs index 1b4e4e8085..b90d1ae229 100644 --- a/Src/LexText/LexTextControls/LiftMergerRanges.cs +++ b/Src/LexText/LexTextControls/LiftMergerRanges.cs @@ -344,7 +344,9 @@ private static ICmPossibility FindOrCreatePossibility(string traitValue, int ws, private static bool TryGetPossibilityMatchingTrait(string traitValue, IDictionary dict, out ICmPossibility poss) { - return dict.TryGetValue(traitValue, out poss) || dict.TryGetValue(traitValue.ToLowerInvariant(), out poss); + string trimmed = traitValue.Trim(); + return dict.TryGetValue(traitValue, out poss) || dict.TryGetValue(traitValue.ToLowerInvariant(), out poss) + || dict.TryGetValue(trimmed, out poss) || dict.TryGetValue(trimmed.ToLowerInvariant(), out poss); } private static void UpdatePossibilityWithTraitValue(int ws, ICmPossibility poss, string traitValue, diff --git a/Src/LexText/LexTextControls/LiftMergerSupportCodeAndClasses.cs b/Src/LexText/LexTextControls/LiftMergerSupportCodeAndClasses.cs index 4622ecd53d..4081df4ae8 100644 --- a/Src/LexText/LexTextControls/LiftMergerSupportCodeAndClasses.cs +++ b/Src/LexText/LexTextControls/LiftMergerSupportCodeAndClasses.cs @@ -145,12 +145,20 @@ private void InitializePossibilityMap(ILcmOwningSequence possibi private static void AddToPossibilityMap(ITsString tss, ICmPossibility poss, Dictionary dict) { if (tss.Length > 0) + AddKeysToPossibilityMap(tss.Text.Normalize(), poss, dict); + } + + /// + /// Add the key as is, lowercased, and trimmed of surrounding whitespace. Some names + /// have stray whitespace (e.g., in semantic domain lists from FieldWorks templates + /// before 2017) that other programs trim before writing LIFT. + /// + private static void AddKeysToPossibilityMap(string key, ICmPossibility poss, Dictionary dict) + { + string trimmed = key.Trim(); + foreach (string s in new[] { key, key.ToLowerInvariant(), trimmed, trimmed.ToLowerInvariant() }) { - string s = tss.Text.Normalize(); - if (!dict.ContainsKey(s)) - dict.Add(s, poss); - s = s.ToLowerInvariant(); - if (!dict.ContainsKey(s)) + if (s.Length > 0 && !dict.ContainsKey(s)) dict.Add(s, poss); } } @@ -198,12 +206,8 @@ private void EnhancePossibilityMapForWeSay(ILcmOwningSequence po { string sAbbr = tssAbbr.Text; string sName = tssName.Text; - string sKey = String.Format("{0} {1}", sAbbr, sName); - if (!dict.ContainsKey(sKey)) - dict.Add(sKey, poss); - sKey = sKey.ToLowerInvariant(); - if (!dict.ContainsKey(sKey)) - dict.Add(sKey, poss); + AddKeysToPossibilityMap(String.Format("{0} {1}", sAbbr, sName), poss, dict); + AddKeysToPossibilityMap(String.Format("{0} {1}", sAbbr.Trim(), sName.Trim()), poss, dict); } } } From 9ec9598cc1f6c19c6a1b76179ab9169751fcd932 Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Fri, 23 Oct 2026 11:16:06 -0400 Subject: [PATCH 2/4] Prefer exact LIFT list matches over trimmed ones Storing trimmed aliases in the possibility maps let an alias claim a key before an item whose name matches exactly, and items created during import got no alias, so whitespace variants within one file still created duplicates. Keep the maps as built from the project, and only when the exact and lowercase lookups miss, scan for a key that matches once trimmed. Co-Authored-By: Claude Opus 5.5 --- .../LexTextControlsTests/LiftMergerTests.cs | 74 +++++++++++++++---- .../LexTextControls/LiftMergerRanges.cs | 20 ++++- .../LiftMergerSupportCodeAndClasses.cs | 26 +++---- 3 files changed, 88 insertions(+), 32 deletions(-) diff --git a/Src/LexText/LexTextControls/LexTextControlsTests/LiftMergerTests.cs b/Src/LexText/LexTextControls/LexTextControlsTests/LiftMergerTests.cs index 6ef6bef39f..fc86e23d13 100644 --- a/Src/LexText/LexTextControls/LexTextControlsTests/LiftMergerTests.cs +++ b/Src/LexText/LexTextControls/LexTextControlsTests/LiftMergerTests.cs @@ -4812,36 +4812,80 @@ public void ImportRangeWithNoId_DoesNotDuplicateGuids_AnthroCode() public void ImportSemanticDomainTrait_MatchesExistingDomainWithUntrimmedName() { SetWritingSystems("fr"); + var semDom = CreateEnglishSemanticDomain("9.4.2.3", "Necessary "); + var semDomCount = Cache.LangProject.SemanticDomainListOA.ReallyReallyAllPossibilities.Count; + + var senses = ImportSenseSemanticDomainTraits("9.4.2.3 Necessary"); + + Assert.That(Cache.LangProject.SemanticDomainListOA.ReallyReallyAllPossibilities, Has.Count.EqualTo(semDomCount), "Should not create a new semantic domain"); + Assert.That(senses.Single().SemanticDomainsRC.Single(), Is.SameAs(semDom)); + } + + [Test] + public void ImportSemanticDomainTrait_PrefersExactMatchOverTrimmedMatch() + { + SetWritingSystems("fr"); + CreateEnglishSemanticDomain("9.4.2.3", "Necessary "); + var exactSemDom = CreateEnglishSemanticDomain("9.4.2.3", "Necessary"); + + var senses = ImportSenseSemanticDomainTraits("9.4.2.3 Necessary"); + + Assert.That(senses.Single().SemanticDomainsRC.Single(), Is.SameAs(exactSemDom)); + } + + [TestCase("9.4.2.3 Necessary ", "9.4.2.3 Necessary")] + [TestCase("9.4.2.3 Necessary", "9.4.2.3 Necessary ")] + public void ImportSemanticDomainTraits_DifferingOnlyInWhitespace_CreateOneDomain(string first, string second) + { + SetWritingSystems("fr"); + var semDomCount = Cache.LangProject.SemanticDomainListOA.ReallyReallyAllPossibilities.Count; + + var senses = ImportSenseSemanticDomainTraits(first, second); + + Assert.That(Cache.LangProject.SemanticDomainListOA.ReallyReallyAllPossibilities, Has.Count.EqualTo(semDomCount + 1)); + Assert.That(senses[1].SemanticDomainsRC.Single(), Is.SameAs(senses[0].SemanticDomainsRC.Single())); + } + + private ICmSemanticDomain CreateEnglishSemanticDomain(string abbr, string name) + { var en = Cache.WritingSystemFactory.GetWsFromStr("en"); - var semDomList = Cache.LangProject.SemanticDomainListOA; var semDom = Cache.ServiceLocator.GetInstance().Create(); - semDomList.PossibilitiesOS.Add(semDom); - semDom.Abbreviation.set_String(en, "9.4.2.3"); - semDom.Name.set_String(en, "Necessary "); - var semDomCount = semDomList.ReallyReallyAllPossibilities.Count; - var liftData = new[] + Cache.LangProject.SemanticDomainListOA.PossibilitiesOS.Add(semDom); + semDom.Abbreviation.set_String(en, abbr); + semDom.Name.set_String(en, name); + return semDom; + } + + /// + /// Import one entry with a sense for each semantic domain trait value, and return its + /// senses in order. + /// + private IList ImportSenseSemanticDomainTraits(params string[] traitValues) + { + var liftData = new List { "", "", "", "", "
baba
", - "
", - "", - "", - "", - "
", - "
" + "" }; + foreach (var traitValue in traitValues) + { + liftData.Add($""); + liftData.Add($""); + liftData.Add(""); + } + liftData.Add(""); + liftData.Add(""); var sOrigFile = CreateInputFile(liftData); var logFile = TryImport(sOrigFile, 1); File.Delete(sOrigFile); File.Delete(logFile); - Assert.That(semDomList.ReallyReallyAllPossibilities, Has.Count.EqualTo(semDomCount), "Should not create a new semantic domain"); - var sense = Cache.ServiceLocator.GetInstance().AllInstances().Single(); - Assert.That(sense.SemanticDomainsRC.Single(), Is.SameAs(semDom)); + return Cache.ServiceLocator.GetInstance().AllInstances().Single().SensesOS.ToList(); } [Test] diff --git a/Src/LexText/LexTextControls/LiftMergerRanges.cs b/Src/LexText/LexTextControls/LiftMergerRanges.cs index b90d1ae229..aba9f8d4bc 100644 --- a/Src/LexText/LexTextControls/LiftMergerRanges.cs +++ b/Src/LexText/LexTextControls/LiftMergerRanges.cs @@ -344,9 +344,25 @@ private static ICmPossibility FindOrCreatePossibility(string traitValue, int ws, private static bool TryGetPossibilityMatchingTrait(string traitValue, IDictionary dict, out ICmPossibility poss) { + if (dict.TryGetValue(traitValue, out poss) || dict.TryGetValue(traitValue.ToLowerInvariant(), out poss)) + return true; + + // Some names have stray whitespace (e.g., in semantic domain lists from FieldWorks + // templates before 2017) that other programs trim before writing LIFT. string trimmed = traitValue.Trim(); - return dict.TryGetValue(traitValue, out poss) || dict.TryGetValue(traitValue.ToLowerInvariant(), out poss) - || dict.TryGetValue(trimmed, out poss) || dict.TryGetValue(trimmed.ToLowerInvariant(), out poss); + if (trimmed.Length == 0) + return false; + string trimmedLower = trimmed.ToLowerInvariant(); + foreach (var kvp in dict) + { + string key = kvp.Key.Trim(); + if (key == trimmed || key == trimmedLower) + { + poss = kvp.Value; + return true; + } + } + return false; } private static void UpdatePossibilityWithTraitValue(int ws, ICmPossibility poss, string traitValue, diff --git a/Src/LexText/LexTextControls/LiftMergerSupportCodeAndClasses.cs b/Src/LexText/LexTextControls/LiftMergerSupportCodeAndClasses.cs index 4081df4ae8..4622ecd53d 100644 --- a/Src/LexText/LexTextControls/LiftMergerSupportCodeAndClasses.cs +++ b/Src/LexText/LexTextControls/LiftMergerSupportCodeAndClasses.cs @@ -145,20 +145,12 @@ private void InitializePossibilityMap(ILcmOwningSequence possibi private static void AddToPossibilityMap(ITsString tss, ICmPossibility poss, Dictionary dict) { if (tss.Length > 0) - AddKeysToPossibilityMap(tss.Text.Normalize(), poss, dict); - } - - /// - /// Add the key as is, lowercased, and trimmed of surrounding whitespace. Some names - /// have stray whitespace (e.g., in semantic domain lists from FieldWorks templates - /// before 2017) that other programs trim before writing LIFT. - /// - private static void AddKeysToPossibilityMap(string key, ICmPossibility poss, Dictionary dict) - { - string trimmed = key.Trim(); - foreach (string s in new[] { key, key.ToLowerInvariant(), trimmed, trimmed.ToLowerInvariant() }) { - if (s.Length > 0 && !dict.ContainsKey(s)) + string s = tss.Text.Normalize(); + if (!dict.ContainsKey(s)) + dict.Add(s, poss); + s = s.ToLowerInvariant(); + if (!dict.ContainsKey(s)) dict.Add(s, poss); } } @@ -206,8 +198,12 @@ private void EnhancePossibilityMapForWeSay(ILcmOwningSequence po { string sAbbr = tssAbbr.Text; string sName = tssName.Text; - AddKeysToPossibilityMap(String.Format("{0} {1}", sAbbr, sName), poss, dict); - AddKeysToPossibilityMap(String.Format("{0} {1}", sAbbr.Trim(), sName.Trim()), poss, dict); + string sKey = String.Format("{0} {1}", sAbbr, sName); + if (!dict.ContainsKey(sKey)) + dict.Add(sKey, poss); + sKey = sKey.ToLowerInvariant(); + if (!dict.ContainsKey(sKey)) + dict.Add(sKey, poss); } } } From 1a307462b28214a11bcd766244ea68dfe994c8f1 Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Fri, 23 Oct 2026 11:34:42 -0400 Subject: [PATCH 3/4] Trim old entry-type values when finding entry types Resolving the old entry-type trait looked up complex form and variant types by exact or lowercase text only, so an existing variant type "Foo " was missed and a new complex form type "Foo" was created. Use the same lookup as the other LIFT trait values. Co-Authored-By: Claude Opus 5.5 --- .../LexTextControlsTests/LiftMergerTests.cs | 42 +++++++++++++++++++ .../LexTextControls/LiftMergerRanges.cs | 18 +++----- 2 files changed, 48 insertions(+), 12 deletions(-) diff --git a/Src/LexText/LexTextControls/LexTextControlsTests/LiftMergerTests.cs b/Src/LexText/LexTextControls/LexTextControlsTests/LiftMergerTests.cs index fc86e23d13..8bb1b2b458 100644 --- a/Src/LexText/LexTextControls/LexTextControlsTests/LiftMergerTests.cs +++ b/Src/LexText/LexTextControls/LexTextControlsTests/LiftMergerTests.cs @@ -4846,6 +4846,48 @@ public void ImportSemanticDomainTraits_DifferingOnlyInWhitespace_CreateOneDomain Assert.That(senses[1].SemanticDomainsRC.Single(), Is.SameAs(senses[0].SemanticDomainsRC.Single())); } + [Test] + public void ImportOldEntryTypeTrait_MatchesExistingVariantTypeWithUntrimmedName() + { + SetWritingSystems("fr"); + var lexDb = Cache.LangProject.LexDbOA; + var listFactory = Cache.ServiceLocator.GetInstance(); + if (lexDb.VariantEntryTypesOA == null) + lexDb.VariantEntryTypesOA = listFactory.Create(); + if (lexDb.ComplexEntryTypesOA == null) + lexDb.ComplexEntryTypesOA = listFactory.Create(); + var variantType = Cache.ServiceLocator.GetInstance().Create(); + lexDb.VariantEntryTypesOA.PossibilitiesOS.Add(variantType); + variantType.Name.set_String(Cache.WritingSystemFactory.GetWsFromStr("en"), "Foo "); + var complexTypeCount = lexDb.ComplexEntryTypesOA.ReallyReallyAllPossibilities.Count; + var liftData = new[] + { + "", + "", + "", + "
baba
", + "
", + "", + "
bebe
", + "", + "", + "
", + "
" + }; + + var sOrigFile = CreateInputFile(liftData); + var logFile = TryImport(sOrigFile, 2); + File.Delete(sOrigFile); + File.Delete(logFile); + + var variantEntry = Cache.ServiceLocator.GetInstance() + .GetObject(new Guid("5e2d8c1a-9b47-4c3e-8f6a-1d0b2e4c7a93")); + var entryRef = variantEntry.EntryRefsOS.Single(); + Assert.That(entryRef.RefType, Is.EqualTo(LexEntryRefTags.krtVariant)); + Assert.That(entryRef.VariantEntryTypesRS.Single(), Is.SameAs(variantType)); + Assert.That(lexDb.ComplexEntryTypesOA.ReallyReallyAllPossibilities, Has.Count.EqualTo(complexTypeCount), "Should not create a new complex form type"); + } + private ICmSemanticDomain CreateEnglishSemanticDomain(string abbr, string name) { var en = Cache.WritingSystemFactory.GetWsFromStr("en"); diff --git a/Src/LexText/LexTextControls/LiftMergerRanges.cs b/Src/LexText/LexTextControls/LiftMergerRanges.cs index aba9f8d4bc..dc781f2368 100644 --- a/Src/LexText/LexTextControls/LiftMergerRanges.cs +++ b/Src/LexText/LexTextControls/LiftMergerRanges.cs @@ -259,23 +259,17 @@ private void FindOrCreateLexRefType(string relationTypeName, string guid, string private ILexEntryType FindComplexFormType(string sOldEntryType) { ICmPossibility poss; - if (m_dictComplexFormType.TryGetValue(sOldEntryType, out poss) || - m_dictComplexFormType.TryGetValue(sOldEntryType.ToLowerInvariant(), out poss)) - { - return poss as ILexEntryType; - } - return null; + return TryGetPossibilityMatchingTrait(sOldEntryType, m_dictComplexFormType, out poss) + ? poss as ILexEntryType + : null; } private ILexEntryType FindVariantType(string sOldEntryType) { ICmPossibility poss; - if (m_dictVariantType.TryGetValue(sOldEntryType, out poss) || - m_dictVariantType.TryGetValue(sOldEntryType.ToLowerInvariant(), out poss)) - { - return poss as ILexEntryType; - } - return null; + return TryGetPossibilityMatchingTrait(sOldEntryType, m_dictVariantType, out poss) + ? poss as ILexEntryType + : null; } private ILexEntryType FindOrCreateComplexFormType(string sType) From 13023b8752ab2261c67ed3cca7ef2022fc3a3ee1 Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Fri, 23 Oct 2026 12:04:54 -0400 Subject: [PATCH 4/4] Prefer exact entry-type matches over trimmed ones Resolving an old entry-type value checked complex form types first, including a trimmed match, so a complex form type "Foo " beat a variant type that matched "Foo" exactly. Try exact matches on both lists before trimmed matches on either; complex form types still win at equal strength. Co-Authored-By: Claude Opus 5.5 --- .../LexTextControlsTests/LiftMergerTests.cs | 70 ++++++++++++++----- .../LexTextControls/LiftMergerRanges.cs | 50 ++++++++----- .../LiftMergerSupportCodeAndClasses.cs | 5 +- 3 files changed, 91 insertions(+), 34 deletions(-) diff --git a/Src/LexText/LexTextControls/LexTextControlsTests/LiftMergerTests.cs b/Src/LexText/LexTextControls/LexTextControlsTests/LiftMergerTests.cs index 8bb1b2b458..42c6a0689a 100644 --- a/Src/LexText/LexTextControls/LexTextControlsTests/LiftMergerTests.cs +++ b/Src/LexText/LexTextControls/LexTextControlsTests/LiftMergerTests.cs @@ -4851,15 +4851,57 @@ public void ImportOldEntryTypeTrait_MatchesExistingVariantTypeWithUntrimmedName( { SetWritingSystems("fr"); var lexDb = Cache.LangProject.LexDbOA; - var listFactory = Cache.ServiceLocator.GetInstance(); - if (lexDb.VariantEntryTypesOA == null) - lexDb.VariantEntryTypesOA = listFactory.Create(); - if (lexDb.ComplexEntryTypesOA == null) - lexDb.ComplexEntryTypesOA = listFactory.Create(); - var variantType = Cache.ServiceLocator.GetInstance().Create(); - lexDb.VariantEntryTypesOA.PossibilitiesOS.Add(variantType); - variantType.Name.set_String(Cache.WritingSystemFactory.GetWsFromStr("en"), "Foo "); - var complexTypeCount = lexDb.ComplexEntryTypesOA.ReallyReallyAllPossibilities.Count; + var variantType = CreateEnglishEntryType(EnsureEntryTypeList(false), "Foo "); + var complexTypeCount = EnsureEntryTypeList(true).ReallyReallyAllPossibilities.Count; + + var entryRef = ImportOldEntryTypeTrait("Foo"); + + Assert.That(entryRef.RefType, Is.EqualTo(LexEntryRefTags.krtVariant)); + Assert.That(entryRef.VariantEntryTypesRS.Single(), Is.SameAs(variantType)); + Assert.That(lexDb.ComplexEntryTypesOA.ReallyReallyAllPossibilities, Has.Count.EqualTo(complexTypeCount), "Should not create a new complex form type"); + } + + [Test] + public void ImportOldEntryTypeTrait_PrefersExactVariantTypeOverTrimmedComplexFormType() + { + SetWritingSystems("fr"); + CreateEnglishEntryType(EnsureEntryTypeList(true), "Foo "); + var variantType = CreateEnglishEntryType(EnsureEntryTypeList(false), "Foo"); + + var entryRef = ImportOldEntryTypeTrait("Foo"); + + Assert.That(entryRef.RefType, Is.EqualTo(LexEntryRefTags.krtVariant)); + Assert.That(entryRef.VariantEntryTypesRS.Single(), Is.SameAs(variantType)); + } + + private ICmPossibilityList EnsureEntryTypeList(bool complex) + { + var lexDb = Cache.LangProject.LexDbOA; + var list = complex ? lexDb.ComplexEntryTypesOA : lexDb.VariantEntryTypesOA; + if (list != null) + return list; + list = Cache.ServiceLocator.GetInstance().Create(); + if (complex) + lexDb.ComplexEntryTypesOA = list; + else + lexDb.VariantEntryTypesOA = list; + return list; + } + + private ILexEntryType CreateEnglishEntryType(ICmPossibilityList list, string name) + { + var entryType = Cache.ServiceLocator.GetInstance().Create(); + list.PossibilitiesOS.Add(entryType); + entryType.Name.set_String(Cache.WritingSystemFactory.GetWsFromStr("en"), name); + return entryType; + } + + /// + /// Import an entry with an old entry-type trait and a component relation to another + /// entry, and return the resulting entry reference. + /// + private ILexEntryRef ImportOldEntryTypeTrait(string entryType) + { var liftData = new[] { "", @@ -4869,7 +4911,7 @@ public void ImportOldEntryTypeTrait_MatchesExistingVariantTypeWithUntrimmedName( "", "", "
bebe
", - "", + $"", "", "
", "" @@ -4880,12 +4922,8 @@ public void ImportOldEntryTypeTrait_MatchesExistingVariantTypeWithUntrimmedName( File.Delete(sOrigFile); File.Delete(logFile); - var variantEntry = Cache.ServiceLocator.GetInstance() - .GetObject(new Guid("5e2d8c1a-9b47-4c3e-8f6a-1d0b2e4c7a93")); - var entryRef = variantEntry.EntryRefsOS.Single(); - Assert.That(entryRef.RefType, Is.EqualTo(LexEntryRefTags.krtVariant)); - Assert.That(entryRef.VariantEntryTypesRS.Single(), Is.SameAs(variantType)); - Assert.That(lexDb.ComplexEntryTypesOA.ReallyReallyAllPossibilities, Has.Count.EqualTo(complexTypeCount), "Should not create a new complex form type"); + return Cache.ServiceLocator.GetInstance() + .GetObject(new Guid("5e2d8c1a-9b47-4c3e-8f6a-1d0b2e4c7a93")).EntryRefsOS.Single(); } private ICmSemanticDomain CreateEnglishSemanticDomain(string abbr, string name) diff --git a/Src/LexText/LexTextControls/LiftMergerRanges.cs b/Src/LexText/LexTextControls/LiftMergerRanges.cs index dc781f2368..f338c29a1e 100644 --- a/Src/LexText/LexTextControls/LiftMergerRanges.cs +++ b/Src/LexText/LexTextControls/LiftMergerRanges.cs @@ -256,20 +256,24 @@ private void FindOrCreateLexRefType(string relationTypeName, string guid, string } - private ILexEntryType FindComplexFormType(string sOldEntryType) - { - ICmPossibility poss; - return TryGetPossibilityMatchingTrait(sOldEntryType, m_dictComplexFormType, out poss) - ? poss as ILexEntryType - : null; - } - - private ILexEntryType FindVariantType(string sOldEntryType) + /// + /// Find the complex form type or variant type named by an old entry-type value, setting + /// at most one of the two. An exact or lowercase match beats a trimmed one; between + /// matches of equal strength, a complex form type beats a variant type. + /// + private void FindOldEntryType(string sOldEntryType, out ILexEntryType letComplex, out ILexEntryType letVar) { + letComplex = null; + letVar = null; ICmPossibility poss; - return TryGetPossibilityMatchingTrait(sOldEntryType, m_dictVariantType, out poss) - ? poss as ILexEntryType - : null; + if (TryGetPossibilityMatchingExactly(sOldEntryType, m_dictComplexFormType, out poss)) + letComplex = poss as ILexEntryType; + else if (TryGetPossibilityMatchingExactly(sOldEntryType, m_dictVariantType, out poss)) + letVar = poss as ILexEntryType; + else if (TryGetPossibilityMatchingTrimmed(sOldEntryType, m_dictComplexFormType, out poss)) + letComplex = poss as ILexEntryType; + else if (TryGetPossibilityMatchingTrimmed(sOldEntryType, m_dictVariantType, out poss)) + letVar = poss as ILexEntryType; } private ILexEntryType FindOrCreateComplexFormType(string sType) @@ -338,11 +342,25 @@ private static ICmPossibility FindOrCreatePossibility(string traitValue, int ws, private static bool TryGetPossibilityMatchingTrait(string traitValue, IDictionary dict, out ICmPossibility poss) { - if (dict.TryGetValue(traitValue, out poss) || dict.TryGetValue(traitValue.ToLowerInvariant(), out poss)) - return true; + return TryGetPossibilityMatchingExactly(traitValue, dict, out poss) + || TryGetPossibilityMatchingTrimmed(traitValue, dict, out poss); + } - // Some names have stray whitespace (e.g., in semantic domain lists from FieldWorks - // templates before 2017) that other programs trim before writing LIFT. + private static bool TryGetPossibilityMatchingExactly(string traitValue, IDictionary dict, + out ICmPossibility poss) + { + return dict.TryGetValue(traitValue, out poss) || dict.TryGetValue(traitValue.ToLowerInvariant(), out poss); + } + + /// + /// Match a key that equals the trait value once both are trimmed of surrounding + /// whitespace. Some names have stray whitespace (e.g., in semantic domain lists from + /// FieldWorks templates before 2017) that other programs trim before writing LIFT. + /// + private static bool TryGetPossibilityMatchingTrimmed(string traitValue, IDictionary dict, + out ICmPossibility poss) + { + poss = null; string trimmed = traitValue.Trim(); if (trimmed.Length == 0) return false; diff --git a/Src/LexText/LexTextControls/LiftMergerSupportCodeAndClasses.cs b/Src/LexText/LexTextControls/LiftMergerSupportCodeAndClasses.cs index 4622ecd53d..b8bcfd6ec5 100644 --- a/Src/LexText/LexTextControls/LiftMergerSupportCodeAndClasses.cs +++ b/Src/LexText/LexTextControls/LiftMergerSupportCodeAndClasses.cs @@ -3083,10 +3083,11 @@ private int DetermineLexEntryTypes(List rgRefs, List