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