From 693c017c04ad1aee08b8d53c2b3124b7c483dadf Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Thu, 20 Nov 2025 14:36:32 -0800 Subject: [PATCH 1/6] Make a minor change to force a new commit to nuget --- src/SIL.LCModel/DomainImpl/OverridesLing_Lex.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SIL.LCModel/DomainImpl/OverridesLing_Lex.cs b/src/SIL.LCModel/DomainImpl/OverridesLing_Lex.cs index 3e5b382a..f7eab80a 100644 --- a/src/SIL.LCModel/DomainImpl/OverridesLing_Lex.cs +++ b/src/SIL.LCModel/DomainImpl/OverridesLing_Lex.cs @@ -205,7 +205,7 @@ public IEnumerable AllPossiblePictures /// /// Gets all the bulk-editable things that might be used as the destination of a bulk edit to - /// Allomorphs. This includes MoForms that are the LexemeForm of some entry. + /// Allomorphs/MoForms. This includes MoForms that are the LexemeForm of some entry. /// [VirtualProperty(CellarPropertyType.ReferenceSequence, "CmObject")] public IEnumerable AllPossibleAllomorphs From d30d8efdd859952ee389397c7ed343ba704a367c Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Mon, 8 Dec 2025 13:02:32 -0800 Subject: [PATCH 2/6] Fix LT-22313: Add FillInBlanks --- src/SIL.LCModel/DomainImpl/OverridesCellar.cs | 63 ++++++++++++++++++- src/SIL.LCModel/InterfaceAdditions.cs | 8 +++ .../DomainImpl/CellarTests.cs | 29 +++++++++ 3 files changed, 98 insertions(+), 2 deletions(-) diff --git a/src/SIL.LCModel/DomainImpl/OverridesCellar.cs b/src/SIL.LCModel/DomainImpl/OverridesCellar.cs index 40599fe1..f85fadaa 100644 --- a/src/SIL.LCModel/DomainImpl/OverridesCellar.cs +++ b/src/SIL.LCModel/DomainImpl/OverridesCellar.cs @@ -3871,8 +3871,8 @@ from myitem in FeatureSpecsOC else { if (myFeatureValues.First() is IFsComplexValue complex && - spec is IFsComplexValue newComplexValue && - complex.ValueOA is IFsFeatStruc fs) + spec is IFsComplexValue newComplexValue && + complex.ValueOA is IFsFeatStruc fs) { fs.PriorityUnion(newComplexValue.ValueOA as IFsFeatStruc); } @@ -3885,6 +3885,65 @@ spec is IFsComplexValue newComplexValue && } } + + /// + /// Fill in the blanks. + /// Remove unfilled blanks and empty feature structures. + /// + /// the values to fill with + public IFsFeatStruc FillInBlanks(IFsFeatStruc fsValue) + { + foreach (IFsFeatureSpecification spec in FeatureSpecsOC.ToList()) + { + // Find matching specification. + IFsFeatureSpecification spec2 = null; + if (fsValue != null) + { + foreach (var spec3 in fsValue.FeatureSpecsOC) + { + if (spec3.FeatureRA.Name == spec.FeatureRA.Name) + { + spec2 = spec3; + } + } + } + // Fill in blanks in spec. + if (spec is IFsClosedValue closed && closed != null && closed.ValueRA == null) + { + if (spec2 is IFsClosedValue closed2 && closed2 != null) + { + // Fill in blank. + closed.ValueRA = closed2.ValueRA; + + } + if (closed.ValueRA == null) + { + // Remove unfilled blank. + FeatureSpecsOC.Remove(spec); + } + } + if (spec is IFsComplexValue complex && complex != null) + { + // Recursively fill in blanks. + if (complex.ValueOA is IFsFeatStruc fs) + { + IFsComplexValue complex2 = spec2 as IFsComplexValue; + if (fs.FillInBlanks(complex2?.ValueOA as IFsFeatStruc) == null) + { + // Remove empty feature structure. + FeatureSpecsOC.Remove(complex); + } + } + } + } + // See if removing blanks emptied the feature structure. + if (FeatureSpecsOC.Count == 0) + { + return null; + } + return this; + } + protected override void RemoveObjectSideEffectsInternal(RemoveObjectEventArgs e) { switch (e.Flid) diff --git a/src/SIL.LCModel/InterfaceAdditions.cs b/src/SIL.LCModel/InterfaceAdditions.cs index d10bd041..9c565171 100644 --- a/src/SIL.LCModel/InterfaceAdditions.cs +++ b/src/SIL.LCModel/InterfaceAdditions.cs @@ -2323,6 +2323,14 @@ ITsString LongNameSortedTSS /// /// the new feature structure void PriorityUnion(IFsFeatStruc fsNew); + + /// + /// Fill in the blanks of this feature structure + /// using the given values. + /// Eliminates blanks with no values and returns null if there is nothing left. + /// + /// feature structure with given values + IFsFeatStruc FillInBlanks(IFsFeatStruc fsValues); } /// diff --git a/tests/SIL.LCModel.Tests/DomainImpl/CellarTests.cs b/tests/SIL.LCModel.Tests/DomainImpl/CellarTests.cs index abc0ba07..d0b93be1 100644 --- a/tests/SIL.LCModel.Tests/DomainImpl/CellarTests.cs +++ b/tests/SIL.LCModel.Tests/DomainImpl/CellarTests.cs @@ -459,6 +459,17 @@ public void AddClosedFeaturesToFeatureSystemAndThenToAFeatureStructure() Assert.AreEqual("Gen", cv.FeatureRA.Abbreviation.AnalysisDefaultWritingSystem.Text, "Expect to have Gen feature name"); Assert.AreEqual("Neut", cv.ValueRA.Abbreviation.AnalysisDefaultWritingSystem.Text, "Expect to have Neut feature value"); } + // Test FillInBlanks. + IFsClosedValue closedValue = featStruct.FeatureSpecsOC.First() as IFsClosedValue; + closedValue.ValueRA = null; + featStruct.FillInBlanks(featStrucGenNeut); + Assert.AreEqual("Agr", featStruct.TypeRA.Abbreviation.AnalysisDefaultWritingSystem.Text, "Expect type Agr"); + Assert.AreEqual(1, featStruct.FeatureSpecsOC.Count, "should have one feature spec"); + foreach (IFsClosedValue cv in featStruct.FeatureSpecsOC) + { + Assert.AreEqual("Gen", cv.FeatureRA.Abbreviation.AnalysisDefaultWritingSystem.Text, "Expect to have Gen feature name"); + Assert.AreEqual("Neut", cv.ValueRA.Abbreviation.AnalysisDefaultWritingSystem.Text, "Expect to have Neut feature value"); + } } /// ------------------------------------------------------------------------------------ @@ -667,6 +678,24 @@ public void AddComplexFeaturesToFeatureSystemAndThenToAFeatureStructure() // Check for correct LongName Assert.AreEqual("[asp:aor sbj:[gen:n num:sg pers:1]]", featStruct.LongName, "Incorrect LongName for merged feature struture"); Assert.AreEqual("[asp:aor sbj:[gen:n num:sg pers:1]]", featStruct.LongNameSorted, "Incorrect LongNameSorted for merged feature struture"); + + // Test FillInBlanks. + pos.DefaultFeaturesOA = null; + pos.DefaultFeaturesOA = Cache.ServiceLocator.GetInstance().Create(); + featStruct = pos.DefaultFeaturesOA; + featStruct.AddFeatureFromXml(itemFem, msfs); + IFsComplexValue complexValue = featStruct.FeatureSpecsOC.First() as IFsComplexValue; + IFsFeatStruc fsValue = complexValue.ValueOA as IFsFeatStruc; + IFsClosedValue closedValue = fsValue.FeatureSpecsOC.First() as IFsClosedValue; + closedValue.ValueRA = null; + featStruct.FillInBlanks(featStruct2); + Assert.AreEqual("[sbj:[gen:n]]", featStruct.LongName, "Incorrect LongName for merged feature struture"); + + // Test removing FillInBlanks. + closedValue.ValueRA = null; + featStruct2.FeatureSpecsOC.Remove(featStruct2.FeatureSpecsOC.First()); + featStruct = featStruct.FillInBlanks(featStruct2); + Assert.AreEqual(null, featStruct, "FillInBlanks didn't remove unfilled blanks"); } From b93d1b0937fcee98ed3babad35ebd25200201afe Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Tue, 9 Dec 2025 08:24:49 -0800 Subject: [PATCH 3/6] Add ContainsBlank --- src/SIL.LCModel/DomainImpl/OverridesCellar.cs | 22 +++++++++++++++++++ src/SIL.LCModel/InterfaceAdditions.cs | 5 +++++ .../DomainImpl/CellarTests.cs | 13 +++++++++-- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/SIL.LCModel/DomainImpl/OverridesCellar.cs b/src/SIL.LCModel/DomainImpl/OverridesCellar.cs index f85fadaa..edf54f08 100644 --- a/src/SIL.LCModel/DomainImpl/OverridesCellar.cs +++ b/src/SIL.LCModel/DomainImpl/OverridesCellar.cs @@ -3944,6 +3944,28 @@ public IFsFeatStruc FillInBlanks(IFsFeatStruc fsValue) return this; } + public bool ContainsBlank() + { + foreach (IFsFeatureSpecification spec in FeatureSpecsOC.ToList()) + { + if (spec is IFsClosedValue closed && closed != null && closed.ValueRA == null) + { + return true; + } + if (spec is IFsComplexValue complex && complex != null) + { + if (complex.ValueOA is IFsFeatStruc fs) + { + if (fs.ContainsBlank()) + { + return true; + } + } + } + } + return false; + } + protected override void RemoveObjectSideEffectsInternal(RemoveObjectEventArgs e) { switch (e.Flid) diff --git a/src/SIL.LCModel/InterfaceAdditions.cs b/src/SIL.LCModel/InterfaceAdditions.cs index 9c565171..e0de10d8 100644 --- a/src/SIL.LCModel/InterfaceAdditions.cs +++ b/src/SIL.LCModel/InterfaceAdditions.cs @@ -2331,6 +2331,11 @@ ITsString LongNameSortedTSS /// /// feature structure with given values IFsFeatStruc FillInBlanks(IFsFeatStruc fsValues); + + /// + /// Determine if this feature structure contains a blank. + /// + public bool ContainsBlank(); } /// diff --git a/tests/SIL.LCModel.Tests/DomainImpl/CellarTests.cs b/tests/SIL.LCModel.Tests/DomainImpl/CellarTests.cs index d0b93be1..11631974 100644 --- a/tests/SIL.LCModel.Tests/DomainImpl/CellarTests.cs +++ b/tests/SIL.LCModel.Tests/DomainImpl/CellarTests.cs @@ -459,10 +459,14 @@ public void AddClosedFeaturesToFeatureSystemAndThenToAFeatureStructure() Assert.AreEqual("Gen", cv.FeatureRA.Abbreviation.AnalysisDefaultWritingSystem.Text, "Expect to have Gen feature name"); Assert.AreEqual("Neut", cv.ValueRA.Abbreviation.AnalysisDefaultWritingSystem.Text, "Expect to have Neut feature value"); } + // Test FillInBlanks. IFsClosedValue closedValue = featStruct.FeatureSpecsOC.First() as IFsClosedValue; closedValue.ValueRA = null; - featStruct.FillInBlanks(featStrucGenNeut); + Assert.IsTrue(featStruct.ContainsBlank()); + Assert.IsFalse(featStrucGenNeut.ContainsBlank()); + featStruct = featStruct.FillInBlanks(featStrucGenNeut); + Assert.IsFalse(featStruct.ContainsBlank()); Assert.AreEqual("Agr", featStruct.TypeRA.Abbreviation.AnalysisDefaultWritingSystem.Text, "Expect type Agr"); Assert.AreEqual(1, featStruct.FeatureSpecsOC.Count, "should have one feature spec"); foreach (IFsClosedValue cv in featStruct.FeatureSpecsOC) @@ -688,12 +692,17 @@ public void AddComplexFeaturesToFeatureSystemAndThenToAFeatureStructure() IFsFeatStruc fsValue = complexValue.ValueOA as IFsFeatStruc; IFsClosedValue closedValue = fsValue.FeatureSpecsOC.First() as IFsClosedValue; closedValue.ValueRA = null; - featStruct.FillInBlanks(featStruct2); + Assert.IsTrue(featStruct.ContainsBlank()); + Assert.IsFalse(featStruct2.ContainsBlank()); + featStruct = featStruct.FillInBlanks(featStruct2); + Assert.IsFalse(featStruct2.ContainsBlank()); Assert.AreEqual("[sbj:[gen:n]]", featStruct.LongName, "Incorrect LongName for merged feature struture"); // Test removing FillInBlanks. closedValue.ValueRA = null; featStruct2.FeatureSpecsOC.Remove(featStruct2.FeatureSpecsOC.First()); + Assert.IsTrue(featStruct.ContainsBlank()); + Assert.IsFalse(featStruct2.ContainsBlank()); featStruct = featStruct.FillInBlanks(featStruct2); Assert.AreEqual(null, featStruct, "FillInBlanks didn't remove unfilled blanks"); } From 68053070d6c8e8380c668c7e86c5d50e627f6f7a Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Thu, 11 Dec 2025 11:40:32 -0800 Subject: [PATCH 4/6] Change ShortName for blank values to include name of attribute --- src/SIL.LCModel/DomainImpl/OverridesCellar.cs | 2 +- tests/SIL.LCModel.Tests/DomainImpl/CellarTests.cs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/SIL.LCModel/DomainImpl/OverridesCellar.cs b/src/SIL.LCModel/DomainImpl/OverridesCellar.cs index edf54f08..9d763cc2 100644 --- a/src/SIL.LCModel/DomainImpl/OverridesCellar.cs +++ b/src/SIL.LCModel/DomainImpl/OverridesCellar.cs @@ -4039,7 +4039,7 @@ orderby FeatureSpecKey(s) var cv = spec as IFsClosedValue; if (cv != null) { - if (fLongForm) + if (fLongForm || cv.ValueRA == null) { tisb.AppendTsString((cv as FsClosedValue)?.LongNameTSS); } diff --git a/tests/SIL.LCModel.Tests/DomainImpl/CellarTests.cs b/tests/SIL.LCModel.Tests/DomainImpl/CellarTests.cs index 11631974..271bd2a6 100644 --- a/tests/SIL.LCModel.Tests/DomainImpl/CellarTests.cs +++ b/tests/SIL.LCModel.Tests/DomainImpl/CellarTests.cs @@ -463,6 +463,7 @@ public void AddClosedFeaturesToFeatureSystemAndThenToAFeatureStructure() // Test FillInBlanks. IFsClosedValue closedValue = featStruct.FeatureSpecsOC.First() as IFsClosedValue; closedValue.ValueRA = null; + Assert.AreEqual("Gen:???", featStruct.ShortName); Assert.IsTrue(featStruct.ContainsBlank()); Assert.IsFalse(featStrucGenNeut.ContainsBlank()); featStruct = featStruct.FillInBlanks(featStrucGenNeut); @@ -692,6 +693,7 @@ public void AddComplexFeaturesToFeatureSystemAndThenToAFeatureStructure() IFsFeatStruc fsValue = complexValue.ValueOA as IFsFeatStruc; IFsClosedValue closedValue = fsValue.FeatureSpecsOC.First() as IFsClosedValue; closedValue.ValueRA = null; + Assert.AreEqual("gen:???", featStruct.ShortName); Assert.IsTrue(featStruct.ContainsBlank()); Assert.IsFalse(featStruct2.ContainsBlank()); featStruct = featStruct.FillInBlanks(featStruct2); @@ -701,6 +703,7 @@ public void AddComplexFeaturesToFeatureSystemAndThenToAFeatureStructure() // Test removing FillInBlanks. closedValue.ValueRA = null; featStruct2.FeatureSpecsOC.Remove(featStruct2.FeatureSpecsOC.First()); + Assert.AreEqual("gen:???", featStruct.ShortName); Assert.IsTrue(featStruct.ContainsBlank()); Assert.IsFalse(featStruct2.ContainsBlank()); featStruct = featStruct.FillInBlanks(featStruct2); From 5eb506ad054e9a96424983098e6e4df043f6a2b1 Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Thu, 11 Dec 2025 11:45:44 -0800 Subject: [PATCH 5/6] Undo edit to comment --- src/SIL.LCModel/DomainImpl/OverridesLing_Lex.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SIL.LCModel/DomainImpl/OverridesLing_Lex.cs b/src/SIL.LCModel/DomainImpl/OverridesLing_Lex.cs index f7eab80a..3e5b382a 100644 --- a/src/SIL.LCModel/DomainImpl/OverridesLing_Lex.cs +++ b/src/SIL.LCModel/DomainImpl/OverridesLing_Lex.cs @@ -205,7 +205,7 @@ public IEnumerable AllPossiblePictures /// /// Gets all the bulk-editable things that might be used as the destination of a bulk edit to - /// Allomorphs/MoForms. This includes MoForms that are the LexemeForm of some entry. + /// Allomorphs. This includes MoForms that are the LexemeForm of some entry. /// [VirtualProperty(CellarPropertyType.ReferenceSequence, "CmObject")] public IEnumerable AllPossibleAllomorphs From da3d38d7f81203a6697149e579b83d10355a551d Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Fri, 12 Dec 2025 07:47:59 -0800 Subject: [PATCH 6/6] Change blank values from name:??? to name:* in FsClosedValue --- src/SIL.LCModel/DomainImpl/OverridesCellar.cs | 7 ++++--- tests/SIL.LCModel.Tests/DomainImpl/CellarTests.cs | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/SIL.LCModel/DomainImpl/OverridesCellar.cs b/src/SIL.LCModel/DomainImpl/OverridesCellar.cs index 9d763cc2..e32fa6d0 100644 --- a/src/SIL.LCModel/DomainImpl/OverridesCellar.cs +++ b/src/SIL.LCModel/DomainImpl/OverridesCellar.cs @@ -2247,8 +2247,9 @@ public ITsString GetFeatureValueString(bool fLongForm) var tisb = TsStringUtils.MakeIncStrBldr(); tisb.SetIntPropValues((int)FwTextPropType.ktptWs, (int)FwTextPropVar.ktpvDefault, Cache.DefaultUserWs); - var sFeature = GetFeatureString(fLongForm); - var sValue = GetValueString(fLongForm); + // ValueRA == null is an empty value that is filled in by FillInBlanks. + var sFeature = GetFeatureString(fLongForm || ValueRA == null); + var sValue = ValueRA == null ? "*" : GetValueString(fLongForm); if ((!fLongForm) && (FeatureRA != null) && (FeatureRA.DisplayToRightOfValues)) @@ -4039,7 +4040,7 @@ orderby FeatureSpecKey(s) var cv = spec as IFsClosedValue; if (cv != null) { - if (fLongForm || cv.ValueRA == null) + if (fLongForm) { tisb.AppendTsString((cv as FsClosedValue)?.LongNameTSS); } diff --git a/tests/SIL.LCModel.Tests/DomainImpl/CellarTests.cs b/tests/SIL.LCModel.Tests/DomainImpl/CellarTests.cs index 271bd2a6..b232f3c4 100644 --- a/tests/SIL.LCModel.Tests/DomainImpl/CellarTests.cs +++ b/tests/SIL.LCModel.Tests/DomainImpl/CellarTests.cs @@ -463,7 +463,7 @@ public void AddClosedFeaturesToFeatureSystemAndThenToAFeatureStructure() // Test FillInBlanks. IFsClosedValue closedValue = featStruct.FeatureSpecsOC.First() as IFsClosedValue; closedValue.ValueRA = null; - Assert.AreEqual("Gen:???", featStruct.ShortName); + Assert.AreEqual("Gen:*", featStruct.ShortName); Assert.IsTrue(featStruct.ContainsBlank()); Assert.IsFalse(featStrucGenNeut.ContainsBlank()); featStruct = featStruct.FillInBlanks(featStrucGenNeut); @@ -693,7 +693,7 @@ public void AddComplexFeaturesToFeatureSystemAndThenToAFeatureStructure() IFsFeatStruc fsValue = complexValue.ValueOA as IFsFeatStruc; IFsClosedValue closedValue = fsValue.FeatureSpecsOC.First() as IFsClosedValue; closedValue.ValueRA = null; - Assert.AreEqual("gen:???", featStruct.ShortName); + Assert.AreEqual("gen:*", featStruct.ShortName); Assert.IsTrue(featStruct.ContainsBlank()); Assert.IsFalse(featStruct2.ContainsBlank()); featStruct = featStruct.FillInBlanks(featStruct2); @@ -703,7 +703,7 @@ public void AddComplexFeaturesToFeatureSystemAndThenToAFeatureStructure() // Test removing FillInBlanks. closedValue.ValueRA = null; featStruct2.FeatureSpecsOC.Remove(featStruct2.FeatureSpecsOC.First()); - Assert.AreEqual("gen:???", featStruct.ShortName); + Assert.AreEqual("gen:*", featStruct.ShortName); Assert.IsTrue(featStruct.ContainsBlank()); Assert.IsFalse(featStruct2.ContainsBlank()); featStruct = featStruct.FillInBlanks(featStruct2);