From 115ffdd477654756cac910d66627cfc4d6cba1b5 Mon Sep 17 00:00:00 2001 From: Ariel Rorabaugh Date: Wed, 14 Oct 2026 13:59:12 -0400 Subject: [PATCH 1/4] LT-22822: Part 1 Update IsFeatureBasedNCUserDefined logic Feature-based natural classes with no name are user-defined, since auto-generated natural classes are always assigned a non-empty name. --- ...RuleFormulaControlNaturalClassNameTests.cs | 34 ++++++------ Src/LexText/Morphology/RuleFormulaControl.cs | 52 ++++++++++++------- Src/LexText/Morphology/RuleFormulaSlice.cs | 2 +- Src/LexText/Morphology/RuleFormulaVcBase.cs | 6 +-- 4 files changed, 55 insertions(+), 39 deletions(-) diff --git a/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaControlNaturalClassNameTests.cs b/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaControlNaturalClassNameTests.cs index 17e2139aa2..1774027f0e 100644 --- a/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaControlNaturalClassNameTests.cs +++ b/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaControlNaturalClassNameTests.cs @@ -21,32 +21,36 @@ public class RuleFormulaControlNaturalClassNameTests : RuleFormulaVcTestBase [Test] public void NoClass_IsNotUserDefined() { - Assert.That(RuleFormulaControl.IsFeatureBasedNCNameUserDefined(null), Is.False); + Assert.That(RuleFormulaControl.IsFeatureBasedNCUserDefined(null), Is.False); } /// - /// The check answers whether a class carries a name the user gave it, and an unnamed - /// class carries none. Set Phonological Features relies on that answer to keep - /// offering feature edits on unnamed classes (LT-22576). + /// A user defined class can have no name, while an auto-generated one will always + /// have a generated name. Check that if the class has no name, it is flagged as user defined. /// [Test] - public void ClassWithNoName_IsNotUserDefined() + public void ClassWithNoName_IsUserDefined() { IPhNCFeatures natClass = AddFeatureNaturalClass(null, "Vd", new FeatVals { { "vd", "+" } }); - Assert.That(RuleFormulaControl.IsFeatureBasedNCNameUserDefined(natClass), Is.False, - "an unnamed class carries no name the user gave it"); + Assert.That(RuleFormulaControl.IsFeatureBasedNCUserDefined(natClass), Is.True, + "an unnamed class must be user defined"); } + /// + /// A user defined class can have an empty name, while an auto-generated one will always + /// have a nonempty generated name. Check that if the class's name is empty, + /// it is flagged as user defined. + /// [Test] - public void ClassWithAnEmptyName_IsNotUserDefined() + public void ClassWithAnEmptyName_IsUserDefined() { IPhNCFeatures natClass = AddFeatureNaturalClass(string.Empty, "Vd", new FeatVals { { "vd", "+" } }); - Assert.That(RuleFormulaControl.IsFeatureBasedNCNameUserDefined(natClass), Is.False, - "an empty name is not a name the user gave the class"); + Assert.That(RuleFormulaControl.IsFeatureBasedNCUserDefined(natClass), Is.True, + "a class with an empty name must be user defined"); } [Test] @@ -55,7 +59,7 @@ public void ClassNamedForARule_IsNotUserDefined() IPhNCFeatures natClass = AddRuleNamedFeatureNaturalClass("s to n", new FeatVals { { "vd", "+" } }); - Assert.That(RuleFormulaControl.IsFeatureBasedNCNameUserDefined(natClass), Is.False); + Assert.That(RuleFormulaControl.IsFeatureBasedNCUserDefined(natClass), Is.False); } /// @@ -70,7 +74,7 @@ public void ClassNamedForARuleBehindAPrefix_IsNotUserDefined() natClass.Name.SetUserWritingSystem("Phonemes s and t - " + string.Format(MEStrings.ksRuleNCFeatsName, "s to n")); - Assert.That(RuleFormulaControl.IsFeatureBasedNCNameUserDefined(natClass), Is.False); + Assert.That(RuleFormulaControl.IsFeatureBasedNCUserDefined(natClass), Is.False); } [Test] @@ -79,7 +83,7 @@ public void NamedClass_IsUserDefined() IPhNCFeatures natClass = AddFeatureNaturalClass("Voiced", "Vd", new FeatVals { { "vd", "+" } }); - Assert.That(RuleFormulaControl.IsFeatureBasedNCNameUserDefined(natClass), Is.True); + Assert.That(RuleFormulaControl.IsFeatureBasedNCUserDefined(natClass), Is.True); } /// @@ -101,7 +105,7 @@ public void ClassNamedInAnAnalysisWritingSystemOtherThanTheUserOne_IsUserDefined "the name must reach the check through the analysis writing system, " + "not through a placeholder"); - Assert.That(RuleFormulaControl.IsFeatureBasedNCNameUserDefined(natClass), Is.True); + Assert.That(RuleFormulaControl.IsFeatureBasedNCUserDefined(natClass), Is.True); } [Test] @@ -109,7 +113,7 @@ public void NamedClassWithNoFeatures_IsUserDefined() { IPhNCFeatures natClass = AddFeatureNaturalClass("Voiced", "Vd", new FeatVals()); - Assert.That(RuleFormulaControl.IsFeatureBasedNCNameUserDefined(natClass), Is.True, + Assert.That(RuleFormulaControl.IsFeatureBasedNCUserDefined(natClass), Is.True, "a name qualifies a class whatever it carries for features"); } } diff --git a/Src/LexText/Morphology/RuleFormulaControl.cs b/Src/LexText/Morphology/RuleFormulaControl.cs index ab19d20a68..27912d5e37 100644 --- a/Src/LexText/Morphology/RuleFormulaControl.cs +++ b/Src/LexText/Morphology/RuleFormulaControl.cs @@ -981,47 +981,59 @@ protected int GetIndexToRemove(ICmObject[] objs, SelectionHelper sel, bool forwa return -1; } /// - /// Returns a bool representing whether the currently selected feature-based natural class simple context is user defined. + /// Returns a bool representing whether the currently selected feature-based + /// natural class simple context is user defined. /// /// - /// A user-defined natural class has a name assigned by the user. - /// Natural classes are auto-generated when a user assigns phonological features, and they receive auto-generated names of a specific form. + /// A user-defined natural class has either no name or a name assigned by the user. + /// Natural classes are auto-generated when a user assigns phonological features, + /// and they receive auto-generated names of a specific form. /// /// - /// Returns false if the natural class is not feature based, or if it has no name or an auto-generated name. - /// Returns true otherwise. + /// Returns false if the natural class is not feature based, + /// or if it has an auto-generated name. + /// Returns true otherwise (including if the class has no name). /// - public bool IsFeatureBasedNCNameUserDefined() + public bool IsFeatureBasedNCUserDefined() { // If the class is not feature based, return false. if (!IsFeatsNCContextCurrent){ return false; } var ctxt = (IPhSimpleContextNC) CurrentContext; - return IsFeatureBasedNCNameUserDefined((IPhNCFeatures)ctxt.FeatureStructureRA); + return IsFeatureBasedNCUserDefined((IPhNCFeatures)ctxt.FeatureStructureRA); } /// - /// Reports whether a feature-based natural class carries a name the user gave it, - /// as opposed to no name or the name generated when phonological features are inserted - /// directly into a rule. Returns false when is null. + /// Reports whether a feature-based natural class is user defined. + /// Returns false when the class's name is auto-generated or + /// is null. Returns true otherwise. /// - public static bool IsFeatureBasedNCNameUserDefined(IPhNCFeatures natClass) + public static bool IsFeatureBasedNCUserDefined(IPhNCFeatures natClass) { if (natClass == null) return false; - // Get the auto-generated text that precedes the rulename. (e.g. extracting "Created automatically for rule "" from "Created automatically for rule "{0}"") - // Note that the generated rulename following the stubtext is different for "regular" vs affix rules, but in all cases the auto-generated stub is the same. - string autoGeneratedNameStub = (MEStrings.ksRuleNCFeatsName).Split('{')[0]; - - // If the natural class name is not null or auto generated, then it is a user defined class name. - // If a class has no name in any WS, it reads back from LCM as the placeholder "***" rather than as empty text, - // so we need to manually check whether the name exists in any available WS to determine if the NC is named. + // Get the name for an autogenerated natural class, localized in the + // user interface language. + // e.g. In English, the name would be "Created automatically for rule "{0}"" + string autoGeneratedName = MEStrings.ksRuleNCFeatsName; + + // Different languages will put the rulename before or after the generated + // stubtext, so we need to check against both pre and post rulename stubs. + int ruleNameIndex = autoGeneratedName.IndexOf("{0}", StringComparison.Ordinal); + if (ruleNameIndex < 0) + return true; // can't recognize generated names in this translation; treat as user defined + string autoGenPreStub = autoGeneratedName.Substring(0, ruleNameIndex); + string autoGenPostStub = autoGeneratedName.Substring(ruleNameIndex + 3); + + // If the NC name is not auto generated, then the class is user defined. + // A user-defined NC may have no name, an auto generated NC always has a + // generated name. var name = natClass.Name; bool isNamed = name != null && name.AvailableWritingSystemIds.Any(ws => !string.IsNullOrEmpty(name.get_String(ws).Text)); string ncName = isNamed ? name.UserDefaultWritingSystem?.Text : null; - bool isAutoGeneratedOrNull = string.IsNullOrEmpty(ncName) || ncName.Contains(autoGeneratedNameStub); - return !isAutoGeneratedOrNull; + bool isAutoGenerated = !string.IsNullOrEmpty(ncName) && ncName.Contains(autoGenPreStub) && ncName.Contains(autoGenPostStub); + return !isAutoGenerated; } /// /// Sets the phonological features for the currently selected natural class simple context with diff --git a/Src/LexText/Morphology/RuleFormulaSlice.cs b/Src/LexText/Morphology/RuleFormulaSlice.cs index 0b1be0bb1b..594f3ea7fa 100644 --- a/Src/LexText/Morphology/RuleFormulaSlice.cs +++ b/Src/LexText/Morphology/RuleFormulaSlice.cs @@ -153,7 +153,7 @@ public bool OnDisplayContextSetFeatures(object commandObject, ref UIItemDisplayP bool enable = false; if (RuleFormulaControl.IsFeatsNCContextCurrent) { - bool isUserDefinedNC = RuleFormulaControl.IsFeatureBasedNCNameUserDefined(); + bool isUserDefinedNC = RuleFormulaControl.IsFeatureBasedNCUserDefined(); enable = !isUserDefinedNC; //enable is true iff IsFeatsNCContextCurrent is true and IsNCNameUserDefined is false } diff --git a/Src/LexText/Morphology/RuleFormulaVcBase.cs b/Src/LexText/Morphology/RuleFormulaVcBase.cs index 007c5be9f4..8b2030d269 100644 --- a/Src/LexText/Morphology/RuleFormulaVcBase.cs +++ b/Src/LexText/Morphology/RuleFormulaVcBase.cs @@ -135,7 +135,7 @@ public override void Display(IVwEnv vwenv, int hvo, int frag) // User-defined feature-based natural classes should display // only the abbreviation. - if (RuleFormulaControl.IsFeatureBasedNCNameUserDefined(natClass)) + if (RuleFormulaControl.IsFeatureBasedNCUserDefined(natClass)) { if (!isOuterIterCtxt) OpenSingleLinePile(vwenv, GetMaxNumLines()); @@ -561,7 +561,7 @@ protected int GetNumLines(IPhContextOrVar ctxtOrVar) // User-defined, feature-based natural classes should display // only the abbreviation, which is a single line. - if (RuleFormulaControl.IsFeatureBasedNCNameUserDefined(natClass)) + if (RuleFormulaControl.IsFeatureBasedNCUserDefined(natClass)) return 1; // Otherwise, the list of features is displayed. @@ -679,7 +679,7 @@ int GetNCCtxtWidth(IPhSimpleContextNC ctxt, IVwEnv vwenv) // User-defined feature-based natural classes display only the abbreviation, // so get width of the abbreviation. var natClass = (IPhNCFeatures)ctxt.FeatureStructureRA; - if (RuleFormulaControl.IsFeatureBasedNCNameUserDefined(natClass)) + if (RuleFormulaControl.IsFeatureBasedNCUserDefined(natClass)) { int len = GetStrWidth(ctxt.FeatureStructureRA.Abbreviation.BestAnalysisAlternative, null, vwenv); len += GetStrWidth(m_leftBracket, null, vwenv); From d6688aa6fff64ae743da7178b12ef9ae99250680 Mon Sep 17 00:00:00 2001 From: Ariel Rorabaugh Date: Tue, 20 Oct 2026 11:03:28 -0400 Subject: [PATCH 2/4] LT-22822: Don't display autogenerated Natural Classes * Hide auto-generated feature-based NCs from displaying in NCs list. * Create a RecordFilter subclass ("ExcludeAutoGeneratedNaturalClassFilter") to filter out auto-gen NCs, and use it to filter the NC list in areaConfiguration.xml. * Hide auto-generated feature-based NCs from the phonological rule NC choosers, so they can't be assigned to other APRs or phonological rules. * Update AffixRuleFormulaControl.SetMappingNaturalClass and the NaturalClass case of RuleFormulaControl.m_insertionControl_Insert to show only NCs that are not auto-generated. --- .../Grammar/areaConfiguration.xml | 3 ++ .../Morphology/AffixRuleFormulaControl.cs | 4 +- .../Morphology/AffixRuleFormulaSlice.cs | 6 ++- .../ExcludeAutoGeneratedNaturalClassFilter.cs | 49 +++++++++++++++++++ Src/LexText/Morphology/RuleFormulaControl.cs | 14 +++++- Src/LexText/Morphology/RuleFormulaSlice.cs | 6 +++ 6 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 Src/LexText/Morphology/ExcludeAutoGeneratedNaturalClassFilter.cs diff --git a/DistFiles/Language Explorer/Configuration/Grammar/areaConfiguration.xml b/DistFiles/Language Explorer/Configuration/Grammar/areaConfiguration.xml index 36a9dc33aa..f0dfdb7b4e 100644 --- a/DistFiles/Language Explorer/Configuration/Grammar/areaConfiguration.xml +++ b/DistFiles/Language Explorer/Configuration/Grammar/areaConfiguration.xml @@ -440,6 +440,9 @@ + + + diff --git a/Src/LexText/Morphology/AffixRuleFormulaControl.cs b/Src/LexText/Morphology/AffixRuleFormulaControl.cs index 96d776c50b..e2dba908c2 100644 --- a/Src/LexText/Morphology/AffixRuleFormulaControl.cs +++ b/Src/LexText/Morphology/AffixRuleFormulaControl.cs @@ -841,7 +841,9 @@ public override void SetMappingNaturalClass(SelectionHelper sel = null) var natClasses = new HashSet(); foreach (var nc in m_cache.LangProject.PhonologicalDataOA.NaturalClassesOS) { - if (nc.ClassID == PhNCFeaturesTags.kClassId) + // Rule mappings only allow feature-based, user-defined natural classes + // to be selected. + if (nc is IPhNCFeatures featNC && IsFeatureBasedNCUserDefined(featNC)) natClasses.Add(nc); } var selectedNc = DisplayChooser(MEStrings.ksRuleNCOpt, MEStrings.ksRuleNCChooserLink, diff --git a/Src/LexText/Morphology/AffixRuleFormulaSlice.cs b/Src/LexText/Morphology/AffixRuleFormulaSlice.cs index 8a54d8fa56..8c7f5a872a 100644 --- a/Src/LexText/Morphology/AffixRuleFormulaSlice.cs +++ b/Src/LexText/Morphology/AffixRuleFormulaSlice.cs @@ -62,7 +62,11 @@ public bool OnMappingSetNaturalClass(object args) public bool OnDisplayMappingJumpToNaturalClass(object commandObject, ref XCore.UIItemDisplayProperties display) { CheckDisposed(); - bool enable = AffixRuleFormulaControl.IsNCIndexCurrent; + // Note that a mapping's Natural Class is always feature-based. + // For feature-based NCs, we only want to allow jumping to the NC + // if it is user-defined. + var mapping = AffixRuleFormulaControl.CurrentObject as IMoModifyFromInput; + bool enable = mapping != null && RuleFormulaControl.IsFeatureBasedNCUserDefined(mapping.ModificationRA); display.Enabled = enable; display.Visible = enable; return true; diff --git a/Src/LexText/Morphology/ExcludeAutoGeneratedNaturalClassFilter.cs b/Src/LexText/Morphology/ExcludeAutoGeneratedNaturalClassFilter.cs new file mode 100644 index 0000000000..c697c88bed --- /dev/null +++ b/Src/LexText/Morphology/ExcludeAutoGeneratedNaturalClassFilter.cs @@ -0,0 +1,49 @@ +// Copyright (c) 2026 SIL International +// This software is licensed under the LGPL, version 2.1 or later +// (http://www.gnu.org/licenses/lgpl-2.1.html) + +using SIL.FieldWorks.Filters; +using SIL.LCModel; + +namespace SIL.FieldWorks.XWorks.MorphologyEditor +{ + /// ---------------------------------------------------------------------------------------- + /// + /// This filter passes every Natural Class except for feature-based Natural Classes + /// that were generated automatically for a rule. + /// + /// ---------------------------------------------------------------------------------------- + public class ExcludeAutoGeneratedNaturalClassFilter : RecordFilter + { + private LcmCache m_cache; + + public override void Init(LcmCache cache, System.Xml.XmlNode filterNode) + { + m_cache = cache; + base.Init(cache, filterNode); + } + + /// + /// Allows the cache to be reset when restoring from persistence. + /// + public override LcmCache Cache + { + set + { + base.Cache = value; + m_cache = value; + } + } + + public override bool Accept(IManyOnePathSortItem item) + { + // Reject the item ONLY if it is a feature-based Natural Class + // that was generated automatically. + var nc = item.RootObjectUsing(m_cache) as IPhNCFeatures; + if (nc != null && !RuleFormulaControl.IsFeatureBasedNCUserDefined(nc)) + return false; + return true; + } + + } +} diff --git a/Src/LexText/Morphology/RuleFormulaControl.cs b/Src/LexText/Morphology/RuleFormulaControl.cs index 27912d5e37..c2167e5960 100644 --- a/Src/LexText/Morphology/RuleFormulaControl.cs +++ b/Src/LexText/Morphology/RuleFormulaControl.cs @@ -613,7 +613,9 @@ private void m_insertionControl_Insert(object sender, InsertEventArgs e) break; case RuleInsertType.NaturalClass: - IEnumerable natClasses = m_cache.LangProject.PhonologicalDataOA.NaturalClassesOS.OrderBy(natc => natc.ShortName); + // show all natural classes that are not auto generated + IEnumerable natClasses = m_cache.LangProject.PhonologicalDataOA.NaturalClassesOS.Where(natc => !IsAutoGeneratedNaturalClass(natc)) + .OrderBy(natc => natc.ShortName); ICmObject ncObj = DisplayChooser(MEStrings.ksRuleNCOpt, MEStrings.ksRuleNCChooserLink, "naturalClassedit", "RuleNaturalClassFlatList", natClasses); var nc = ncObj as IPhNaturalClass; @@ -1035,6 +1037,16 @@ public static bool IsFeatureBasedNCUserDefined(IPhNCFeatures natClass) bool isAutoGenerated = !string.IsNullOrEmpty(ncName) && ncName.Contains(autoGenPreStub) && ncName.Contains(autoGenPostStub); return !isAutoGenerated; } + + /// + /// True for feature-based natural classes generated automatically for a rule. + /// Phoneme-based classes are never auto-generated. + /// + public static bool IsAutoGeneratedNaturalClass(IPhNaturalClass nc) + { + return nc is IPhNCFeatures featNC && !IsFeatureBasedNCUserDefined(featNC); + } + /// /// Sets the phonological features for the currently selected natural class simple context with /// a feature-based natural class. diff --git a/Src/LexText/Morphology/RuleFormulaSlice.cs b/Src/LexText/Morphology/RuleFormulaSlice.cs index 594f3ea7fa..3ad0e079ae 100644 --- a/Src/LexText/Morphology/RuleFormulaSlice.cs +++ b/Src/LexText/Morphology/RuleFormulaSlice.cs @@ -173,6 +173,12 @@ public bool OnDisplayContextJumpToNaturalClass(object commandObject, ref UIItemD { CheckDisposed(); bool enable = RuleFormulaControl.IsNCContextCurrent; + + // If the current context is a feature-based natural class, + // we only want to allow jumping to the natural class if it is user-defined. + if (RuleFormulaControl.IsFeatsNCContextCurrent) + enable = RuleFormulaControl.IsFeatureBasedNCUserDefined(); + display.Enabled = enable; display.Visible = enable; return true; From c2f87eb17f8f593e0cc24896f948c96306754fc4 Mon Sep 17 00:00:00 2001 From: Ariel Rorabaugh Date: Wed, 21 Oct 2026 09:31:25 -0400 Subject: [PATCH 3/4] LT-22822: Add tests for hiding auto-generated Natural Classes Cover ExcludeAutoGeneratedNaturalClassFilter: it rejects auto-generated classes, passes named, unnamed and segment-based classes, and still works when it receives its cache only through the Cache setter, as a filter restored from persisted settings does. Move the name matching out of IsFeatureBasedNCUserDefined into an internal IsAutoGeneratedName(ncName, format) helper, so it can be tested against format strings other than the English one. The behavior of IsFeatureBasedNCUserDefined is unchanged. Add tests that a null class is not auto-generated, that a format starting with the rule name recognizes generated names without matching ordinary ones, and that a format with no rule name placeholder recognizes nothing. Co-Authored-By: Claude Opus 5.5 --- ...udeAutoGeneratedNaturalClassFilterTests.cs | 84 +++++++++++++++++++ ...RuleFormulaControlNaturalClassNameTests.cs | 36 ++++++++ Src/LexText/Morphology/RuleFormulaControl.cs | 40 +++++---- 3 files changed, 146 insertions(+), 14 deletions(-) create mode 100644 Src/LexText/Morphology/MorphologyEditorDllTests/ExcludeAutoGeneratedNaturalClassFilterTests.cs diff --git a/Src/LexText/Morphology/MorphologyEditorDllTests/ExcludeAutoGeneratedNaturalClassFilterTests.cs b/Src/LexText/Morphology/MorphologyEditorDllTests/ExcludeAutoGeneratedNaturalClassFilterTests.cs new file mode 100644 index 0000000000..46c6bfcafb --- /dev/null +++ b/Src/LexText/Morphology/MorphologyEditorDllTests/ExcludeAutoGeneratedNaturalClassFilterTests.cs @@ -0,0 +1,84 @@ +// Copyright (c) 2026 SIL International +// This software is licensed under the LGPL, version 2.1 or later +// (http://www.gnu.org/licenses/lgpl-2.1.html) + +using NUnit.Framework; +using SIL.FieldWorks.Filters; +using SIL.LCModel; +using FeatVals = System.Collections.Generic.Dictionary; + +namespace SIL.FieldWorks.XWorks.MorphologyEditor +{ + /// + /// Pins which natural classes pass: + /// every class except a feature-based one generated automatically for a rule. + /// + [TestFixture] + public class ExcludeAutoGeneratedNaturalClassFilterTests : RuleFormulaVcTestBase + { + private ExcludeAutoGeneratedNaturalClassFilter CreateFilter() + { + var filter = new ExcludeAutoGeneratedNaturalClassFilter(); + filter.Init(Cache, null); + return filter; + } + + private static bool Accepts(RecordFilter filter, ICmObject natClass) + { + return filter.Accept(new ManyOnePathSortItem(natClass)); + } + + [Test] + public void AutoGeneratedClass_IsRejected() + { + IPhNCFeatures natClass = AddRuleNamedFeatureNaturalClass("s to n", + new FeatVals { { "vd", "+" } }); + + Assert.That(Accepts(CreateFilter(), natClass), Is.False); + } + + [Test] + public void NamedFeatureClass_IsAccepted() + { + IPhNCFeatures natClass = AddFeatureNaturalClass("Voiced", "Vd", + new FeatVals { { "vd", "+" } }); + + Assert.That(Accepts(CreateFilter(), natClass), Is.True); + } + + /// + /// A feature-based class the user creates starts with no name, so an unnamed class must + /// pass for the user to be able to name it. + /// + [Test] + public void UnnamedFeatureClass_IsAccepted() + { + IPhNCFeatures natClass = AddFeatureNaturalClass(null, null, + new FeatVals { { "vd", "+" } }); + + Assert.That(Accepts(CreateFilter(), natClass), Is.True); + } + + [Test] + public void SegmentClass_IsAccepted() + { + IPhNCSegments natClass = AddSegmentNaturalClass("Vowels", "V"); + + Assert.That(Accepts(CreateFilter(), natClass), Is.True); + } + + /// + /// A filter restored from persisted settings is not initialized from a configuration + /// node; it receives its cache through the Cache setter alone. + /// + [Test] + public void RestoredFilter_UsesCacheFromSetter() + { + IPhNCFeatures natClass = AddRuleNamedFeatureNaturalClass("s to n", + new FeatVals { { "vd", "+" } }); + var filter = new ExcludeAutoGeneratedNaturalClassFilter { Cache = Cache }; + + Assert.That(Accepts(filter, natClass), Is.False); + } + } +} diff --git a/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaControlNaturalClassNameTests.cs b/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaControlNaturalClassNameTests.cs index 1774027f0e..c10a458e7d 100644 --- a/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaControlNaturalClassNameTests.cs +++ b/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaControlNaturalClassNameTests.cs @@ -116,5 +116,41 @@ public void NamedClassWithNoFeatures_IsUserDefined() Assert.That(RuleFormulaControl.IsFeatureBasedNCUserDefined(natClass), Is.True, "a name qualifies a class whatever it carries for features"); } + + /// + /// No class is neither user defined nor auto-generated. + /// + [Test] + public void NullClass_IsNotAutoGenerated() + { + Assert.That(RuleFormulaControl.IsAutoGeneratedNaturalClass(null), Is.False); + } + + /// + /// A translation may put the rule name first, leaving no generated text before the + /// placeholder. Its generated names are still recognized, but ordinary names are not. + /// + [Test] + public void FormatStartingWithRuleName_RecognisesGeneratedName() + { + const string format = "{0} rule: created automatically"; + + Assert.That(RuleFormulaControl.IsAutoGeneratedName(string.Format(format, "s to n"), format), + Is.True, "a name generated from the format is recognized"); + Assert.That(RuleFormulaControl.IsAutoGeneratedName("Voiced", format), Is.False, + "an empty stub before the rule name must not match every name"); + } + + /// + /// A translation lacking the rule name placeholder must not break recognition; no name + /// counts as generated from it. + /// + [Test] + public void FormatWithoutPlaceholder_RecognisesNothing() + { + const string format = "Created automatically for rule"; + + Assert.That(RuleFormulaControl.IsAutoGeneratedName(format, format), Is.False); + } } } diff --git a/Src/LexText/Morphology/RuleFormulaControl.cs b/Src/LexText/Morphology/RuleFormulaControl.cs index c2167e5960..1be1e17319 100644 --- a/Src/LexText/Morphology/RuleFormulaControl.cs +++ b/Src/LexText/Morphology/RuleFormulaControl.cs @@ -1015,27 +1015,39 @@ public static bool IsFeatureBasedNCUserDefined(IPhNCFeatures natClass) if (natClass == null) return false; + // If the NC name is not auto generated, then the class is user defined. + // A user-defined NC may have no name, an auto generated NC always has a + // generated name. + var name = natClass.Name; + bool isNamed = name != null && name.AvailableWritingSystemIds.Any(ws => !string.IsNullOrEmpty(name.get_String(ws).Text)); + string ncName = isNamed ? name.UserDefaultWritingSystem?.Text : null; + // Get the name for an autogenerated natural class, localized in the // user interface language. // e.g. In English, the name would be "Created automatically for rule "{0}"" - string autoGeneratedName = MEStrings.ksRuleNCFeatsName; + return !IsAutoGeneratedName(ncName, MEStrings.ksRuleNCFeatsName); + } + + /// + /// Reports whether is a name generated from + /// , the format string auto-generated natural classes are + /// named with (its rule name placeholder is "{0}"). The generated text may sit anywhere + /// in the name. Returns false for a null or empty name, and for a format with no + /// placeholder, since generated names can't be recognized from it. + /// + internal static bool IsAutoGeneratedName(string ncName, string format) + { + if (string.IsNullOrEmpty(ncName)) + return false; // Different languages will put the rulename before or after the generated // stubtext, so we need to check against both pre and post rulename stubs. - int ruleNameIndex = autoGeneratedName.IndexOf("{0}", StringComparison.Ordinal); + int ruleNameIndex = format.IndexOf("{0}", StringComparison.Ordinal); if (ruleNameIndex < 0) - return true; // can't recognize generated names in this translation; treat as user defined - string autoGenPreStub = autoGeneratedName.Substring(0, ruleNameIndex); - string autoGenPostStub = autoGeneratedName.Substring(ruleNameIndex + 3); - - // If the NC name is not auto generated, then the class is user defined. - // A user-defined NC may have no name, an auto generated NC always has a - // generated name. - var name = natClass.Name; - bool isNamed = name != null && name.AvailableWritingSystemIds.Any(ws => !string.IsNullOrEmpty(name.get_String(ws).Text)); - string ncName = isNamed ? name.UserDefaultWritingSystem?.Text : null; - bool isAutoGenerated = !string.IsNullOrEmpty(ncName) && ncName.Contains(autoGenPreStub) && ncName.Contains(autoGenPostStub); - return !isAutoGenerated; + return false; // can't recognize generated names in this translation + string autoGenPreStub = format.Substring(0, ruleNameIndex); + string autoGenPostStub = format.Substring(ruleNameIndex + 3); + return ncName.Contains(autoGenPreStub) && ncName.Contains(autoGenPostStub); } /// From cf36e637cf68dc8c8ea4094674c321b995359701 Mon Sep 17 00:00:00 2001 From: Ariel Rorabaugh Date: Wed, 21 Oct 2026 11:49:11 -0400 Subject: [PATCH 4/4] LT-22822: Exclude autogen NCs from record list instead of via filter If we use a default hidden filter to hide auto-generated NCs, there are a couple of code paths that will circumvent the filter and cause autogen NCs to re-display in the NCs list. Instead, we remove them at the source--using a dynamic loader for the NC record list. Update tests to match new behavior. For clarity, rename test helper method from AddRuleNamedFeatureNaturalClass to AddAutoGeneratedFeatureNaturalClass. Co-Authored-By: Claude Opus 5.5 --- .../Grammar/areaConfiguration.xml | 7 +- .../ExcludeAutoGeneratedNaturalClassFilter.cs | 49 ----------- ...udeAutoGeneratedNaturalClassFilterTests.cs | 84 ------------------- .../NaturalClassRecordListTests.cs | 64 ++++++++++++++ ...RuleFormulaControlNaturalClassNameTests.cs | 6 +- ...uleFormulaVcNaturalClassDependencyTests.cs | 2 +- .../RuleFormulaVcNaturalClassDisplayTests.cs | 10 +-- .../RuleFormulaVcNaturalClassSizingTests.cs | 4 +- .../RuleFormulaVcTestBase.cs | 6 +- .../Morphology/NaturalClassRecordList.cs | 32 +++++++ 10 files changed, 113 insertions(+), 151 deletions(-) delete mode 100644 Src/LexText/Morphology/ExcludeAutoGeneratedNaturalClassFilter.cs delete mode 100644 Src/LexText/Morphology/MorphologyEditorDllTests/ExcludeAutoGeneratedNaturalClassFilterTests.cs create mode 100644 Src/LexText/Morphology/MorphologyEditorDllTests/NaturalClassRecordListTests.cs create mode 100644 Src/LexText/Morphology/NaturalClassRecordList.cs diff --git a/DistFiles/Language Explorer/Configuration/Grammar/areaConfiguration.xml b/DistFiles/Language Explorer/Configuration/Grammar/areaConfiguration.xml index f0dfdb7b4e..1c9dfebc13 100644 --- a/DistFiles/Language Explorer/Configuration/Grammar/areaConfiguration.xml +++ b/DistFiles/Language Explorer/Configuration/Grammar/areaConfiguration.xml @@ -439,10 +439,9 @@ - - - - + + + diff --git a/Src/LexText/Morphology/ExcludeAutoGeneratedNaturalClassFilter.cs b/Src/LexText/Morphology/ExcludeAutoGeneratedNaturalClassFilter.cs deleted file mode 100644 index c697c88bed..0000000000 --- a/Src/LexText/Morphology/ExcludeAutoGeneratedNaturalClassFilter.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) 2026 SIL International -// This software is licensed under the LGPL, version 2.1 or later -// (http://www.gnu.org/licenses/lgpl-2.1.html) - -using SIL.FieldWorks.Filters; -using SIL.LCModel; - -namespace SIL.FieldWorks.XWorks.MorphologyEditor -{ - /// ---------------------------------------------------------------------------------------- - /// - /// This filter passes every Natural Class except for feature-based Natural Classes - /// that were generated automatically for a rule. - /// - /// ---------------------------------------------------------------------------------------- - public class ExcludeAutoGeneratedNaturalClassFilter : RecordFilter - { - private LcmCache m_cache; - - public override void Init(LcmCache cache, System.Xml.XmlNode filterNode) - { - m_cache = cache; - base.Init(cache, filterNode); - } - - /// - /// Allows the cache to be reset when restoring from persistence. - /// - public override LcmCache Cache - { - set - { - base.Cache = value; - m_cache = value; - } - } - - public override bool Accept(IManyOnePathSortItem item) - { - // Reject the item ONLY if it is a feature-based Natural Class - // that was generated automatically. - var nc = item.RootObjectUsing(m_cache) as IPhNCFeatures; - if (nc != null && !RuleFormulaControl.IsFeatureBasedNCUserDefined(nc)) - return false; - return true; - } - - } -} diff --git a/Src/LexText/Morphology/MorphologyEditorDllTests/ExcludeAutoGeneratedNaturalClassFilterTests.cs b/Src/LexText/Morphology/MorphologyEditorDllTests/ExcludeAutoGeneratedNaturalClassFilterTests.cs deleted file mode 100644 index 46c6bfcafb..0000000000 --- a/Src/LexText/Morphology/MorphologyEditorDllTests/ExcludeAutoGeneratedNaturalClassFilterTests.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) 2026 SIL International -// This software is licensed under the LGPL, version 2.1 or later -// (http://www.gnu.org/licenses/lgpl-2.1.html) - -using NUnit.Framework; -using SIL.FieldWorks.Filters; -using SIL.LCModel; -using FeatVals = System.Collections.Generic.Dictionary; - -namespace SIL.FieldWorks.XWorks.MorphologyEditor -{ - /// - /// Pins which natural classes pass: - /// every class except a feature-based one generated automatically for a rule. - /// - [TestFixture] - public class ExcludeAutoGeneratedNaturalClassFilterTests : RuleFormulaVcTestBase - { - private ExcludeAutoGeneratedNaturalClassFilter CreateFilter() - { - var filter = new ExcludeAutoGeneratedNaturalClassFilter(); - filter.Init(Cache, null); - return filter; - } - - private static bool Accepts(RecordFilter filter, ICmObject natClass) - { - return filter.Accept(new ManyOnePathSortItem(natClass)); - } - - [Test] - public void AutoGeneratedClass_IsRejected() - { - IPhNCFeatures natClass = AddRuleNamedFeatureNaturalClass("s to n", - new FeatVals { { "vd", "+" } }); - - Assert.That(Accepts(CreateFilter(), natClass), Is.False); - } - - [Test] - public void NamedFeatureClass_IsAccepted() - { - IPhNCFeatures natClass = AddFeatureNaturalClass("Voiced", "Vd", - new FeatVals { { "vd", "+" } }); - - Assert.That(Accepts(CreateFilter(), natClass), Is.True); - } - - /// - /// A feature-based class the user creates starts with no name, so an unnamed class must - /// pass for the user to be able to name it. - /// - [Test] - public void UnnamedFeatureClass_IsAccepted() - { - IPhNCFeatures natClass = AddFeatureNaturalClass(null, null, - new FeatVals { { "vd", "+" } }); - - Assert.That(Accepts(CreateFilter(), natClass), Is.True); - } - - [Test] - public void SegmentClass_IsAccepted() - { - IPhNCSegments natClass = AddSegmentNaturalClass("Vowels", "V"); - - Assert.That(Accepts(CreateFilter(), natClass), Is.True); - } - - /// - /// A filter restored from persisted settings is not initialized from a configuration - /// node; it receives its cache through the Cache setter alone. - /// - [Test] - public void RestoredFilter_UsesCacheFromSetter() - { - IPhNCFeatures natClass = AddRuleNamedFeatureNaturalClass("s to n", - new FeatVals { { "vd", "+" } }); - var filter = new ExcludeAutoGeneratedNaturalClassFilter { Cache = Cache }; - - Assert.That(Accepts(filter, natClass), Is.False); - } - } -} diff --git a/Src/LexText/Morphology/MorphologyEditorDllTests/NaturalClassRecordListTests.cs b/Src/LexText/Morphology/MorphologyEditorDllTests/NaturalClassRecordListTests.cs new file mode 100644 index 0000000000..9ad1952579 --- /dev/null +++ b/Src/LexText/Morphology/MorphologyEditorDllTests/NaturalClassRecordListTests.cs @@ -0,0 +1,64 @@ +// Copyright (c) 2026 SIL International +// This software is licensed under the LGPL, version 2.1 or later +// (http://www.gnu.org/licenses/lgpl-2.1.html) + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using SIL.LCModel; + +namespace SIL.FieldWorks.XWorks.MorphologyEditor +{ + /// + /// Tests to confirm that includes every + /// natural class except a feature-based one generated automatically for a rule. + /// + [TestFixture] + public class NaturalClassRecordListTests : RuleFormulaVcTestBase + { + private IEnumerable NCRecordList(params ICmObject[] natClasses) + { + return NaturalClassRecordList.ExcludeAutoGenerated( + natClasses.Select(nc => nc.Hvo), Cache); + } + [Test] + public void AutoGeneratedClass_IsExcluded() + { + IPhNCFeatures natClass = AddAutoGeneratedFeatureNaturalClass("s to n", + new Dictionary { { "vd", "+" } }); + + // Since the only NC is autogenerated, the NC record list should be empty + Assert.That(NCRecordList(natClass), Is.Empty); + } + + [Test] + public void NamedFeatureClass_IsIncluded() + { + IPhNCFeatures natClass = AddFeatureNaturalClass("Voiced", "Vd", + new Dictionary { { "vd", "+" } }); + + Assert.That(NCRecordList(natClass), Is.EqualTo(new[] { natClass.Hvo })); + } + + /// + /// A feature-based class the user creates starts with no name, so an unnamed class must + /// be included for the user to be able to name it. + /// + [Test] + public void UnnamedFeatureClass_IsIncluded() + { + IPhNCFeatures natClass = AddFeatureNaturalClass(null, null, + new Dictionary { { "vd", "+" } }); + + Assert.That(NCRecordList(natClass), Is.EqualTo(new[] { natClass.Hvo })); + } + + [Test] + public void SegmentClass_IsIncluded() + { + IPhNCSegments natClass = AddSegmentNaturalClass("Vowels", "V"); + + Assert.That(NCRecordList(natClass), Is.EqualTo(new[] { natClass.Hvo })); + } + } +} diff --git a/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaControlNaturalClassNameTests.cs b/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaControlNaturalClassNameTests.cs index c10a458e7d..c85d4c7d70 100644 --- a/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaControlNaturalClassNameTests.cs +++ b/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaControlNaturalClassNameTests.cs @@ -25,8 +25,8 @@ public void NoClass_IsNotUserDefined() } /// - /// A user defined class can have no name, while an auto-generated one will always - /// have a generated name. Check that if the class has no name, it is flagged as user defined. + /// A user defined class can have no name, while an auto-generated one will always have + /// a generated name. Check that if the class has no name, it is flagged as user defined. /// [Test] public void ClassWithNoName_IsUserDefined() @@ -56,7 +56,7 @@ public void ClassWithAnEmptyName_IsUserDefined() [Test] public void ClassNamedForARule_IsNotUserDefined() { - IPhNCFeatures natClass = AddRuleNamedFeatureNaturalClass("s to n", + IPhNCFeatures natClass = AddAutoGeneratedFeatureNaturalClass("s to n", new FeatVals { { "vd", "+" } }); Assert.That(RuleFormulaControl.IsFeatureBasedNCUserDefined(natClass), Is.False); diff --git a/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaVcNaturalClassDependencyTests.cs b/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaVcNaturalClassDependencyTests.cs index c8f2f59105..39ae080cfc 100644 --- a/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaVcNaturalClassDependencyTests.cs +++ b/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaVcNaturalClassDependencyTests.cs @@ -40,7 +40,7 @@ public void RegularRule_RegistersTheNameAndAbbreviationOfItsClass() [Test] public void RegularRule_RegistersTheNameOfARuleNamedClass() { - IPhNCFeatures natClass = AddRuleNamedFeatureNaturalClass("s to n", + IPhNCFeatures natClass = AddAutoGeneratedFeatureNaturalClass("s to n", new FeatVals { { "vd", "+" } }); IPhSegRuleRHS rhs = AddRegularRule("s to n"); AddStrucDescNCContext(rhs, natClass); diff --git a/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaVcNaturalClassDisplayTests.cs b/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaVcNaturalClassDisplayTests.cs index d4f6469427..e831a3f8e2 100644 --- a/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaVcNaturalClassDisplayTests.cs +++ b/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaVcNaturalClassDisplayTests.cs @@ -67,7 +67,7 @@ public void NamedClass_WithEmptyAbbreviation_DrawsStars() [Test] public void RuleNamedClass_WithOneFeature_DrawsFeature() { - IPhNCFeatures natClass = AddRuleNamedFeatureNaturalClass("s to n", + IPhNCFeatures natClass = AddAutoGeneratedFeatureNaturalClass("s to n", new FeatVals { { "vd", "+" } }); Assert.That(DrawContext(AddStandaloneNCContext(natClass)), Is.EqualTo("[+ vd]")); @@ -76,7 +76,7 @@ public void RuleNamedClass_WithOneFeature_DrawsFeature() [Test] public void RuleNamedClass_WithThreeFeatures_DrawsEveryFeatureInAPile() { - IPhNCFeatures natClass = AddRuleNamedFeatureNaturalClass("s to n", + IPhNCFeatures natClass = AddAutoGeneratedFeatureNaturalClass("s to n", new FeatVals { { "vd", "+" }, { "cons", "+" }, { "cont", "-" } }); string drawn = DrawContext(AddStandaloneNCContext(natClass)); @@ -92,7 +92,7 @@ public void RuleNamedClass_WithThreeFeatures_DrawsEveryFeatureInAPile() [Test] public void RuleNamedClass_WithNoFeatures_DrawsQuestions() { - IPhNCFeatures natClass = AddRuleNamedFeatureNaturalClass("s to n", new FeatVals()); + IPhNCFeatures natClass = AddAutoGeneratedFeatureNaturalClass("s to n", new FeatVals()); Assert.That(DrawContext(AddStandaloneNCContext(natClass)), Is.EqualTo("[???]")); } @@ -105,7 +105,7 @@ public void RuleNamedClass_WithNoFeatures_DrawsQuestions() [Test] public void RuleNamedClass_AbbreviatedC_DrawsFeature() { - IPhNCFeatures natClass = AddRuleNamedFeatureNaturalClass("s to n", + IPhNCFeatures natClass = AddAutoGeneratedFeatureNaturalClass("s to n", new FeatVals { { "cons", "+" } }); natClass.Abbreviation.SetAnalysisDefaultWritingSystem("C"); @@ -119,7 +119,7 @@ public void RuleNamedClass_AbbreviatedC_DrawsFeature() [Test] public void RuleNamedClass_OnceNamed_DrawsAbbreviation() { - IPhNCFeatures natClass = AddRuleNamedFeatureNaturalClass("s to n", + IPhNCFeatures natClass = AddAutoGeneratedFeatureNaturalClass("s to n", new FeatVals { { "vd", "+" } }); IPhSimpleContextNC ctxt = AddStandaloneNCContext(natClass); Assert.That(DrawContext(ctxt), Is.EqualTo("[+ vd]"), "before the class is named"); diff --git a/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaVcNaturalClassSizingTests.cs b/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaVcNaturalClassSizingTests.cs index 991e79e2e4..8d5c23287e 100644 --- a/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaVcNaturalClassSizingTests.cs +++ b/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaVcNaturalClassSizingTests.cs @@ -52,7 +52,7 @@ public void NamedClass_WithNoAbbreviation_IsSizedToTheStars() [Test] public void RuleNamedClass_WithOneFeature_IsSizedToItsFeature() { - IPhNCFeatures natClass = AddRuleNamedFeatureNaturalClass("s to n", + IPhNCFeatures natClass = AddAutoGeneratedFeatureNaturalClass("s to n", new FeatVals { { "vd", "+" } }); AssertCellMatchesDrawing(AddStandaloneNCContext(natClass), "[+ vd]"); @@ -70,7 +70,7 @@ public void NamedClass_WithThreeFeatures_OccupiesOneLine() [Test] public void RuleNamedClass_WithThreeFeatures_OccupiesOneLinePerFeature() { - IPhNCFeatures natClass = AddRuleNamedFeatureNaturalClass("s to n", + IPhNCFeatures natClass = AddAutoGeneratedFeatureNaturalClass("s to n", new FeatVals { { "vd", "+" }, { "cons", "+" }, { "cont", "-" } }); Assert.That(NumLinesFor(AddStandaloneNCContext(natClass)), Is.EqualTo(3)); diff --git a/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaVcTestBase.cs b/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaVcTestBase.cs index 7ba2075b2c..54c8d2348c 100644 --- a/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaVcTestBase.cs +++ b/Src/LexText/Morphology/MorphologyEditorDllTests/RuleFormulaVcTestBase.cs @@ -93,10 +93,10 @@ protected IPhNCFeatures AddFeatureNaturalClass(string name, string abbreviation, } /// - /// Adds a feature-based natural class named the way one built for a rule is named, in the - /// user writing system and with no abbreviation. + /// Adds a feature-based natural class named the way one auto-generated for a rule + /// is named, in the user writing system and with no abbreviation. /// - protected IPhNCFeatures AddRuleNamedFeatureNaturalClass(string ruleName, FeatVals featVals) + protected IPhNCFeatures AddAutoGeneratedFeatureNaturalClass(string ruleName, FeatVals featVals) { IPhNCFeatures natClass = AddFeatureNaturalClass(null, null, featVals); natClass.Name.SetUserWritingSystem(string.Format(MEStrings.ksRuleNCFeatsName, ruleName)); diff --git a/Src/LexText/Morphology/NaturalClassRecordList.cs b/Src/LexText/Morphology/NaturalClassRecordList.cs new file mode 100644 index 0000000000..5e9f392552 --- /dev/null +++ b/Src/LexText/Morphology/NaturalClassRecordList.cs @@ -0,0 +1,32 @@ +// Copyright (c) 2026 SIL International +// This software is licensed under the LGPL, version 2.1 or later +// (http://www.gnu.org/licenses/lgpl-2.1.html) + +using System.Collections.Generic; +using System.Linq; +using SIL.LCModel; + +namespace SIL.FieldWorks.XWorks.MorphologyEditor +{ + /// + /// Exclude all auto-generated feature-based natural classes from the NC record list. + /// + public class NaturalClassRecordList : RecordList + { + protected override IEnumerable GetObjectSet() + { + return ExcludeAutoGenerated(base.GetObjectSet(), m_cache); + } + + /// + /// The given natural class HVOs, less the feature-based classes generated automatically + /// for a rule. + /// + internal static IEnumerable ExcludeAutoGenerated(IEnumerable hvos, LcmCache cache) + { + var repo = cache.ServiceLocator.GetInstance(); + return hvos.Where(hvo => + !RuleFormulaControl.IsAutoGeneratedNaturalClass(repo.GetObject(hvo) as IPhNaturalClass)); + } + } +}