diff --git a/Src/LexText/LexTextControls/LexTextControlsTests/LiftMergerTests.cs b/Src/LexText/LexTextControls/LexTextControlsTests/LiftMergerTests.cs
index 582069e9cf..42c6a0689a 100644
--- a/Src/LexText/LexTextControls/LexTextControlsTests/LiftMergerTests.cs
+++ b/Src/LexText/LexTextControls/LexTextControlsTests/LiftMergerTests.cs
@@ -4803,6 +4803,171 @@ 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 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()));
+ }
+
+ [Test]
+ public void ImportOldEntryTypeTrait_MatchesExistingVariantTypeWithUntrimmedName()
+ {
+ SetWritingSystems("fr");
+ var lexDb = Cache.LangProject.LexDbOA;
+ 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[]
+ {
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ $"",
+ "",
+ "",
+ ""
+ };
+
+ var sOrigFile = CreateInputFile(liftData);
+ var logFile = TryImport(sOrigFile, 2);
+ File.Delete(sOrigFile);
+ File.Delete(logFile);
+
+ return Cache.ServiceLocator.GetInstance()
+ .GetObject(new Guid("5e2d8c1a-9b47-4c3e-8f6a-1d0b2e4c7a93")).EntryRefsOS.Single();
+ }
+
+ private ICmSemanticDomain CreateEnglishSemanticDomain(string abbr, string name)
+ {
+ var en = Cache.WritingSystemFactory.GetWsFromStr("en");
+ var semDom = Cache.ServiceLocator.GetInstance().Create();
+ 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
+ {
+ "",
+ "",
+ "",
+ "",
+ "",
+ ""
+ };
+ 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);
+
+ return Cache.ServiceLocator.GetInstance().AllInstances().Single().SensesOS.ToList();
+ }
+
[Test]
public void ImportRangeWithExistingObject_DoesNotDuplicate_UnifiesData()
{
diff --git a/Src/LexText/LexTextControls/LiftMergerRanges.cs b/Src/LexText/LexTextControls/LiftMergerRanges.cs
index 1b4e4e8085..f338c29a1e 100644
--- a/Src/LexText/LexTextControls/LiftMergerRanges.cs
+++ b/Src/LexText/LexTextControls/LiftMergerRanges.cs
@@ -256,26 +256,24 @@ 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;
- }
-
- 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;
- if (m_dictVariantType.TryGetValue(sOldEntryType, out poss) ||
- m_dictVariantType.TryGetValue(sOldEntryType.ToLowerInvariant(), out poss))
- {
- return poss as ILexEntryType;
- }
- return 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)
@@ -343,10 +341,42 @@ private static ICmPossibility FindOrCreatePossibility(string traitValue, int ws,
private static bool TryGetPossibilityMatchingTrait(string traitValue, IDictionary dict,
out ICmPossibility poss)
+ {
+ return TryGetPossibilityMatchingExactly(traitValue, dict, out poss)
+ || TryGetPossibilityMatchingTrimmed(traitValue, dict, out poss);
+ }
+
+ 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;
+ 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,
IDictionary dict, ICollection rgnewPoss)
{
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