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[] + { + "", + "", + "", + "
baba
", + "
", + "", + "
bebe
", + $"", + "", + "
", + "
" + }; + + 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 + { + "", + "", + "", + "", + "
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); + + 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