diff --git a/Src/LexText/LexTextControls/LexTextControlsTests/LiftMergerTests.cs b/Src/LexText/LexTextControls/LexTextControlsTests/LiftMergerTests.cs
index 582069e9cf..e1e1fe60b2 100644
--- a/Src/LexText/LexTextControls/LexTextControlsTests/LiftMergerTests.cs
+++ b/Src/LexText/LexTextControls/LexTextControlsTests/LiftMergerTests.cs
@@ -4803,6 +4803,333 @@ 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_MatchesExistingDomainWithPaddedName()
+ {
+ 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_MatchesPaddedNameIgnoringCase()
+ {
+ 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_PrefersUntrimmedMatchOverTrimmedMatch()
+ {
+ 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 ImportGrammaticalInfo_MatchesExistingPartOfSpeechWithPaddedName()
+ {
+ SetWritingSystems("fr");
+ var posList = Cache.LangProject.PartsOfSpeechOA;
+ var pos = Cache.ServiceLocator.GetInstance().Create();
+ posList.PossibilitiesOS.Add(pos);
+ pos.Name.set_String(Cache.WritingSystemFactory.GetWsFromStr("en"), "Foo ");
+ var posCount = posList.ReallyReallyAllPossibilities.Count;
+ var liftData = new[]
+ {
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ ""
+ };
+
+ var sOrigFile = CreateInputFile(liftData);
+ var logFile = TryImport(sOrigFile, 1);
+ File.Delete(sOrigFile);
+ File.Delete(logFile);
+
+ var sense = Cache.ServiceLocator.GetInstance().AllInstances().Single().SensesOS.Single();
+ Assert.That(sense.MorphoSyntaxAnalysisRA, Is.AssignableTo());
+ Assert.That(((IMoStemMsa)sense.MorphoSyntaxAnalysisRA).PartOfSpeechRA, Is.SameAs(pos));
+ Assert.That(posList.ReallyReallyAllPossibilities, Has.Count.EqualTo(posCount), "Should not create a new part of speech");
+ }
+
+ [Test]
+ public void ImportOldEntryTypeTrait_MatchesExistingComplexFormTypeWithPaddedName()
+ {
+ SetWritingSystems("fr");
+ var lexDb = Cache.LangProject.LexDbOA;
+ var complexType = CreateEnglishEntryType(EnsureEntryTypeList(true), "Foo ");
+ var complexTypeCount = lexDb.ComplexEntryTypesOA.ReallyReallyAllPossibilities.Count;
+ var variantTypeCount = EnsureEntryTypeList(false).ReallyReallyAllPossibilities.Count;
+
+ var entryRef = ImportOldEntryTypeTrait("Foo");
+
+ Assert.That(entryRef.RefType, Is.EqualTo(LexEntryRefTags.krtComplexForm));
+ Assert.That(entryRef.ComplexEntryTypesRS.Single(), Is.SameAs(complexType));
+ Assert.That(lexDb.ComplexEntryTypesOA.ReallyReallyAllPossibilities, Has.Count.EqualTo(complexTypeCount), "Should not create a new complex form type");
+ Assert.That(lexDb.VariantEntryTypesOA.ReallyReallyAllPossibilities, Has.Count.EqualTo(variantTypeCount), "Should not create a new variant type");
+ }
+
+ [Test]
+ public void ImportOldEntryTypeTrait_MatchesExistingVariantTypeWithPaddedName()
+ {
+ 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");
+ }
+
+ [TestCase("Foo")]
+ [TestCase("Foo ")]
+ public void ImportOldEntryTypeTrait_PrefersComplexFormTypeOverVariantTypeOfEqualMatch(string existingName)
+ {
+ SetWritingSystems("fr");
+ var complexType = CreateEnglishEntryType(EnsureEntryTypeList(true), existingName);
+ CreateEnglishEntryType(EnsureEntryTypeList(false), existingName);
+
+ var entryRef = ImportOldEntryTypeTrait("Foo");
+
+ Assert.That(entryRef.RefType, Is.EqualTo(LexEntryRefTags.krtComplexForm));
+ Assert.That(entryRef.ComplexEntryTypesRS.Single(), Is.SameAs(complexType));
+ }
+
+ [Test]
+ public void ImportOldEntryTypeTrait_PrefersUntrimmedVariantTypeOverTrimmedComplexFormType()
+ {
+ 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));
+ }
+
+ [Test]
+ public void ImportOldEntryTypeTrait_PaddedValue_PrefersUnpaddedType()
+ {
+ SetWritingSystems("fr");
+ EnsureEntryTypeList(true);
+ var variantList = EnsureEntryTypeList(false);
+ CreateEnglishEntryType(variantList, " Foo");
+ var variantType = CreateEnglishEntryType(variantList, "Foo");
+
+ var entryRef = ImportOldEntryTypeTrait("Foo ");
+
+ Assert.That(entryRef.RefType, Is.EqualTo(LexEntryRefTags.krtVariant));
+ Assert.That(entryRef.VariantEntryTypesRS.Single(), Is.SameAs(variantType));
+ }
+
+ [Test]
+ public void ImportOldEntryTypeTraitWithCondition_AddsNewSubtypeToPaddedVariantType()
+ {
+ SetWritingSystems("fr");
+ EnsureEntryTypeList(true);
+ var variantType = CreateEnglishEntryType(EnsureEntryTypeList(false), "Foo ");
+
+ var entryRef = ImportOldEntryTypeTrait("Foo", "Bar");
+
+ Assert.That(entryRef.RefType, Is.EqualTo(LexEntryRefTags.krtVariant));
+ var subtype = entryRef.VariantEntryTypesRS.Single();
+ Assert.That(subtype.Owner, Is.SameAs(variantType));
+ Assert.That(subtype.Name.AnalysisDefaultWritingSystem.Text, Is.EqualTo("Bar"));
+ }
+
+ [Test]
+ public void ImportOldEntryTypeTraitWithCondition_ReusesSubtypeOfPaddedVariantType()
+ {
+ SetWritingSystems("fr");
+ EnsureEntryTypeList(true);
+ var variantType = CreateEnglishEntryType(EnsureEntryTypeList(false), "Foo ");
+ var subtype = Cache.ServiceLocator.GetInstance().Create();
+ variantType.SubPossibilitiesOS.Add(subtype);
+ subtype.Name.set_String(Cache.WritingSystemFactory.GetWsFromStr("en"), "Bar");
+
+ var entryRef = ImportOldEntryTypeTrait("Foo", "Bar");
+
+ Assert.That(entryRef.RefType, Is.EqualTo(LexEntryRefTags.krtVariant));
+ Assert.That(entryRef.VariantEntryTypesRS.Single(), Is.SameAs(subtype));
+ Assert.That(variantType.SubPossibilitiesOS, Has.Count.EqualTo(1), "Should not create a new subtype");
+ }
+
+ [Test]
+ public void ImportOldEntryTypeTrait_NoMatch_CreatesComplexFormType()
+ {
+ SetWritingSystems("fr");
+ var lexDb = Cache.LangProject.LexDbOA;
+ var complexTypeCount = EnsureEntryTypeList(true).ReallyReallyAllPossibilities.Count;
+ var variantTypeCount = EnsureEntryTypeList(false).ReallyReallyAllPossibilities.Count;
+
+ var entryRef = ImportOldEntryTypeTrait("Bar");
+
+ Assert.That(entryRef.RefType, Is.EqualTo(LexEntryRefTags.krtComplexForm));
+ Assert.That(entryRef.ComplexEntryTypesRS.Single().Name.AnalysisDefaultWritingSystem.Text, Is.EqualTo("Bar"));
+ Assert.That(lexDb.ComplexEntryTypesOA.ReallyReallyAllPossibilities, Has.Count.EqualTo(complexTypeCount + 1));
+ Assert.That(lexDb.VariantEntryTypesOA.ReallyReallyAllPossibilities, Has.Count.EqualTo(variantTypeCount), "Should not create a new variant type");
+ }
+
+ [Test]
+ public void ImportOldEntryTypeTraitWithCondition_NoMatch_CreatesVariantTypeWithSubtype()
+ {
+ SetWritingSystems("fr");
+ var lexDb = Cache.LangProject.LexDbOA;
+ var complexTypeCount = EnsureEntryTypeList(true).ReallyReallyAllPossibilities.Count;
+ var variantTypeCount = EnsureEntryTypeList(false).ReallyReallyAllPossibilities.Count;
+
+ var entryRef = ImportOldEntryTypeTrait("Bar", "Baz");
+
+ Assert.That(entryRef.RefType, Is.EqualTo(LexEntryRefTags.krtVariant));
+ var subtype = entryRef.VariantEntryTypesRS.Single();
+ Assert.That(subtype.Name.AnalysisDefaultWritingSystem.Text, Is.EqualTo("Baz"));
+ var variantType = (ILexEntryType)subtype.Owner;
+ Assert.That(variantType.Name.AnalysisDefaultWritingSystem.Text, Is.EqualTo("Bar"));
+ Assert.That(variantType.Owner, Is.SameAs(lexDb.VariantEntryTypesOA));
+ Assert.That(lexDb.VariantEntryTypesOA.ReallyReallyAllPossibilities, Has.Count.EqualTo(variantTypeCount + 2));
+ Assert.That(lexDb.ComplexEntryTypesOA.ReallyReallyAllPossibilities, Has.Count.EqualTo(complexTypeCount), "Should not create a new complex form type");
+ }
+
+ 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, an optional minor-entry-condition trait,
+ /// and a component relation to another entry, and return the resulting entry reference.
+ ///
+ private ILexEntryRef ImportOldEntryTypeTrait(string entryType, string condition = null)
+ {
+ var liftData = new List
+ {
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ $""
+ };
+ if (condition != null)
+ liftData.Add($"");
+ liftData.Add("");
+ liftData.Add("");
+ liftData.Add("");
+
+ 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
+ {
+ "",
+ "",
+ "",
+ "",
+ "",
+ ""
+ };
+ for (var i = 0; i < traitValues.Length; ++i)
+ {
+ 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..04084dc887 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 (TryGetPossibilityMatchingUntrimmed(sOldEntryType, m_dictComplexFormType, out poss))
+ letComplex = poss as ILexEntryType;
+ else if (TryGetPossibilityMatchingUntrimmed(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,45 @@ private static ICmPossibility FindOrCreatePossibility(string traitValue, int ws,
private static bool TryGetPossibilityMatchingTrait(string traitValue, IDictionary dict,
out ICmPossibility poss)
+ {
+ return TryGetPossibilityMatchingUntrimmed(traitValue, dict, out poss)
+ || TryGetPossibilityMatchingTrimmed(traitValue, dict, out poss);
+ }
+
+ private static bool TryGetPossibilityMatchingUntrimmed(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, or its lowercase form, once both are trimmed
+ /// of surrounding whitespace; a key with no surrounding whitespace wins. 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;
+ if (TryGetPossibilityMatchingUntrimmed(trimmed, dict, out poss))
+ return true;
+ 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