From fd517004b31558c6c7fd13967a030b421986a127 Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Mon, 21 Sep 2026 07:40:23 -0700 Subject: [PATCH 1/8] Add UniqueStemMSAs --- Src/LexText/ParserCore/HCLoader.cs | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/Src/LexText/ParserCore/HCLoader.cs b/Src/LexText/ParserCore/HCLoader.cs index 4d2a889c92..ec2bc68794 100644 --- a/Src/LexText/ParserCore/HCLoader.cs +++ b/Src/LexText/ParserCore/HCLoader.cs @@ -636,7 +636,7 @@ protected virtual void LoadLexEntries(Stratum stratum, ILexEntry entry, IList()) + foreach (IMoStemMsa msa in UniqueStemMSAs(mainEntry.MorphoSyntaxAnalysesOC.OfType())) LoadLexEntryOfVariant(stratum, inflType, msa, allos, entry.ShortName); } else @@ -650,10 +650,32 @@ protected virtual void LoadLexEntries(Stratum stratum, ILexEntry entry, IList()) + foreach (IMoStemMsa msa in UniqueStemMSAs(entry.MorphoSyntaxAnalysesOC.OfType())) LoadLexEntry(stratum, msa, allos, entry.ShortName); } + IEnumerable UniqueStemMSAs(IEnumerable msas) + { + IList newMsas = new List(); + foreach (var msa in msas) + { + bool found = false; + foreach (var newMsa in newMsas) + { + if (newMsa.EqualsMsa(msa)) + { + found = true; + break; + } + } + if (!found) + { + newMsas.Add(msa); + } + } + return newMsas; + } + protected IEnumerable GetInflTypes(ILexEntryRef lexEntryRef) { if (lexEntryRef.VariantEntryTypesRS.Count == 0) From c6b7755e7e51e6b14cfb18f73704bdea599e0585 Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Mon, 28 Sep 2026 08:33:23 -0700 Subject: [PATCH 2/8] Fixes bugs reported by John Lambert --- Src/LexText/ParserCore/HCLoader.cs | 72 ++++++++++++++++++++------- Src/LexText/ParserCore/HCParser.cs | 6 ++- Src/LexText/ParserCore/ParseResult.cs | 19 ++++++- 3 files changed, 75 insertions(+), 22 deletions(-) diff --git a/Src/LexText/ParserCore/HCLoader.cs b/Src/LexText/ParserCore/HCLoader.cs index ec2bc68794..13df43615b 100644 --- a/Src/LexText/ParserCore/HCLoader.cs +++ b/Src/LexText/ParserCore/HCLoader.cs @@ -34,6 +34,17 @@ public static Language Load(LcmCache cache, IHCLoadErrorLogger logger) return loader.m_language; } + public static Language Load( + LcmCache cache, + IHCLoadErrorLogger logger, + out Dictionary representativeMSAs) + { + var loader = new HCLoader(cache, logger); + loader.LoadLanguage(); + representativeMSAs = loader.m_representativeMSAs; + return loader.m_language; + } + private static readonly string[] VariableNames = { "α", "β", "γ", "δ", "ε", "ζ", "η", "θ", "ι", "κ", "λ", "μ", "ν", "ξ", @@ -43,6 +54,7 @@ public static Language Load(LcmCache cache, IHCLoadErrorLogger logger) private readonly LcmCache m_cache; protected readonly Dictionary> m_allomorphs; private readonly Dictionary> m_morphemes; + protected readonly Dictionary m_representativeMSAs; private readonly Dictionary m_stemNames; protected readonly Dictionary m_mprFeatures; @@ -78,6 +90,7 @@ protected HCLoader(LcmCache cache, IHCLoadErrorLogger logger) m_logger = logger; m_allomorphs = new Dictionary>(); m_morphemes = new Dictionary>(); + m_representativeMSAs = new Dictionary(); m_stemNames = new Dictionary(); m_mprFeatures = new Dictionary(); @@ -643,7 +656,12 @@ protected virtual void LoadLexEntries(Stratum stratum, ILexEntry entry, IList UniqueStemMSAs(IEnumerable msas) + IMoMorphSynAnalysis RepresentativeMSA(IMoMorphSynAnalysis msa) { - IList newMsas = new List(); - foreach (var msa in msas) + if (m_representativeMSAs.TryGetValue(msa, out IMoMorphSynAnalysis representativeMSA)) { - bool found = false; - foreach (var newMsa in newMsas) - { - if (newMsa.EqualsMsa(msa)) - { - found = true; - break; - } - } - if (!found) + return representativeMSA; + } + ILexEntry entry = msa.OwnerOfClass(); + foreach (var msa2 in entry.MorphoSyntaxAnalysesOC) + { + if (msa2.EqualsMsa(msa) && entry.SenseWithMsa(msa2) != null) { - newMsas.Add(msa); + m_representativeMSAs[msa] = msa2; + return msa2; } } + return msa; + } + + IEnumerable UniqueStemMSAs(IEnumerable msas) + { + HashSet newMsas = new HashSet(); + foreach (var msa in msas) + { + newMsas.Add((IMoStemMsa)RepresentativeMSA(msa)); + } + return newMsas; + } + + IEnumerable UniqueMSAs(IEnumerable msas) + { + HashSet newMsas = new HashSet(); + foreach (var msa in msas) + { + newMsas.Add(RepresentativeMSA(msa)); + } return newMsas; } @@ -880,19 +914,19 @@ protected virtual void LoadMorphologicalRules(Stratum stratum, ILexEntry entry, var mainEntry = component as ILexEntry; if (mainEntry != null) { - foreach (IMoMorphSynAnalysis msa in mainEntry.MorphoSyntaxAnalysesOC) + foreach (IMoMorphSynAnalysis msa in UniqueMSAs(mainEntry.MorphoSyntaxAnalysesOC)) LoadMorphologicalRule(stratum, entry, allos, msa); } else { var sense = (ILexSense)component; - LoadMorphologicalRule(stratum, entry, allos, sense.MorphoSyntaxAnalysisRA); + LoadMorphologicalRule(stratum, entry, allos, RepresentativeMSA(sense.MorphoSyntaxAnalysisRA)); } } } } - foreach (IMoMorphSynAnalysis msa in entry.MorphoSyntaxAnalysesOC) + foreach (IMoMorphSynAnalysis msa in UniqueMSAs(entry.MorphoSyntaxAnalysesOC)) LoadMorphologicalRule(stratum, entry, allos, msa); } @@ -2238,7 +2272,7 @@ private void LoadMorphemeCoOccurrenceRules(IMoMorphAdhocProhib morphAdhocProhib) if (m_morphemes.TryGetValue(morphAdhocProhib.FirstMorphemeRA, out firstMorphemes)) { var allOthers = new List>(); - foreach (IMoMorphSynAnalysis msa in morphAdhocProhib.RestOfMorphsRS) + foreach (IMoMorphSynAnalysis msa in UniqueMSAs(morphAdhocProhib.RestOfMorphsRS)) { List hcMorphemes; if (m_morphemes.TryGetValue(msa, out hcMorphemes)) diff --git a/Src/LexText/ParserCore/HCParser.cs b/Src/LexText/ParserCore/HCParser.cs index dd09d1c9ea..c2a3394a65 100644 --- a/Src/LexText/ParserCore/HCParser.cs +++ b/Src/LexText/ParserCore/HCParser.cs @@ -25,6 +25,7 @@ public class HCParser : DisposableBase, IParser private readonly LcmCache m_cache; private Morpher m_morpher; private Language m_language; + private Dictionary m_representativeMSAs; private readonly FwXmlTraceManager m_traceManager; private readonly string m_outputDirectory; private ParserModelChangeListener m_changeListener; @@ -109,7 +110,8 @@ public ParseResult ParseWord(string word) if (GetMorphs(wordAnalysis, out morphs)) { analyses.Add(new ParseAnalysis(morphs.Select(mi => - new ParseMorph(mi.Form, mi.Msa, mi.InflType, mi.GuessedString)))); + new ParseMorph(mi.Form, mi.Msa, mi.InflType, mi.GuessedString)), + m_representativeMSAs)); } } result = new ParseResult(analyses); @@ -156,7 +158,7 @@ private void LoadParser() using (new WorkerThreadReadHelper(m_cache.ServiceLocator.GetInstance())) { writer.WriteStartElement("LoadErrors"); - m_language = HCLoader.Load(m_cache, new XmlHCLoadErrorLogger(writer)); + m_language = HCLoader.Load(m_cache, new XmlHCLoadErrorLogger(writer), out m_representativeMSAs); writer.WriteEndElement(); XElement parserParamsElem = XElement.Parse(m_cache.LanguageProject.MorphologicalDataOA.ParserParameters); XElement delReappsElem = parserParamsElem.Elements("HC").Elements("DelReapps").FirstOrDefault(); diff --git a/Src/LexText/ParserCore/ParseResult.cs b/Src/LexText/ParserCore/ParseResult.cs index a29e287f31..77116350c5 100644 --- a/Src/LexText/ParserCore/ParseResult.cs +++ b/Src/LexText/ParserCore/ParseResult.cs @@ -72,12 +72,19 @@ public override int GetHashCode() public class ParseAnalysis : IEquatable { private readonly ReadOnlyCollection m_morphs; + private readonly Dictionary m_representativeMSAs; public ParseAnalysis(IEnumerable morphs) { m_morphs = new ReadOnlyCollection(morphs.ToArray()); } + public ParseAnalysis(IEnumerable morphs, Dictionary representativeMSAs) + { + m_morphs = new ReadOnlyCollection(morphs.ToArray()); + m_representativeMSAs = representativeMSAs; + } + public ReadOnlyCollection Morphs { get { return m_morphs; } @@ -115,7 +122,8 @@ public bool MatchesIWfiAnalysis(IWfiAnalysis analysis) foreach (IWfiMorphBundle mb in analysis.MorphBundlesOS) { var current = this.Morphs[i++]; - if (mb.MorphRA == current.Form && mb.MsaRA == current.Msa && mb.InflTypeRA == current.InflType && + var mbMSA = RepresentativeMSA(mb.MsaRA); + if (mb.MorphRA == current.Form && mbMSA == current.Msa && mb.InflTypeRA == current.InflType && (current.GuessedString == null || EquivalentFormString(mb.Form, current.GuessedString))) { // Possibly matches condition (2), above. @@ -132,6 +140,15 @@ public bool MatchesIWfiAnalysis(IWfiAnalysis analysis) return false; } + private IMoMorphSynAnalysis RepresentativeMSA(IMoMorphSynAnalysis msa) + { + if (msa != null && m_representativeMSAs != null && m_representativeMSAs.TryGetValue(msa, out IMoMorphSynAnalysis repMSA)) + { + return repMSA; + } + return msa; + } + private bool EquivalentFormString(IMultiString multiString, string formString) { foreach (int ws in multiString.AvailableWritingSystemIds) From cc961f353c2044c33175541d72e0b97ecdabd92f Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Tue, 29 Sep 2026 07:50:55 -0700 Subject: [PATCH 3/8] Add unit test --- .../ParserCoreTests/HCLoaderTests.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Src/LexText/ParserCore/ParserCoreTests/HCLoaderTests.cs b/Src/LexText/ParserCore/ParserCoreTests/HCLoaderTests.cs index 45550323d2..954fb07194 100644 --- a/Src/LexText/ParserCore/ParserCoreTests/HCLoaderTests.cs +++ b/Src/LexText/ParserCore/ParserCoreTests/HCLoaderTests.cs @@ -662,6 +662,28 @@ public void UnclassifedAffix() Assert.That(rule.IsPartial, Is.True); } + [Test] + public void RepresentativeMSAs() + { + ILexEntry lexEntry = AddEntry(MoMorphTypeTags.kguidMorphSuffix, "ɯd", "gloss", new SandboxGenericMSA { MsaType = MsaType.kUnclassified, MainPOS = m_verb }); + var senseFactory = Cache.ServiceLocator.GetInstance(); + var lexSense = senseFactory.Create(); + lexEntry.SensesOS.Add(lexSense); + IMoUnclassifiedAffixMsa msa = Cache.ServiceLocator.GetInstance().Create(); + lexEntry.MorphoSyntaxAnalysesOC.Add(msa); + lexSense.MorphoSyntaxAnalysisRA = msa; + msa.PartOfSpeechRA = m_verb; + + LoadLanguage(); + + Assert.That(m_lang.Strata[0].MorphologicalRules.Count, Is.EqualTo(1)); + var rule = (AffixProcessRule)m_lang.Strata[0].MorphologicalRules[0]; + + Assert.That(rule.RequiredSyntacticFeatureStruct.ToString(), Is.EqualTo("[POS:V]")); + Assert.That(rule.Gloss, Is.EqualTo("gloss")); + Assert.That(rule.IsPartial, Is.True); + } + [Test] public void AffixNoMorphTypeSet() { From 05b26fe275e5e1dfccebf410ccda51e35b6158f3 Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Tue, 29 Sep 2026 08:57:36 -0700 Subject: [PATCH 4/8] Add MergeMSAs to HC parser parameters --- Src/LexText/ParserCore/HCLoader.cs | 10 +++++++--- Src/LexText/ParserCore/HCParser.cs | 19 +++++++++++++++---- Src/LexText/ParserUI/ParserParametersDlg.cs | 9 +++++++-- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/Src/LexText/ParserCore/HCLoader.cs b/Src/LexText/ParserCore/HCLoader.cs index 13df43615b..d2020ab456 100644 --- a/Src/LexText/ParserCore/HCLoader.cs +++ b/Src/LexText/ParserCore/HCLoader.cs @@ -39,7 +39,7 @@ public static Language Load( IHCLoadErrorLogger logger, out Dictionary representativeMSAs) { - var loader = new HCLoader(cache, logger); + var loader = new HCLoader(cache, logger, true); loader.LoadLanguage(); representativeMSAs = loader.m_representativeMSAs; return loader.m_language; @@ -84,13 +84,13 @@ public static Language Load( private CharacterDefinition m_morphBdry; protected static char m_ReplaceSpaceChar = '.'; - protected HCLoader(LcmCache cache, IHCLoadErrorLogger logger) + protected HCLoader(LcmCache cache, IHCLoadErrorLogger logger, bool mergeMSAs = false) { m_cache = cache; m_logger = logger; m_allomorphs = new Dictionary>(); m_morphemes = new Dictionary>(); - m_representativeMSAs = new Dictionary(); + m_representativeMSAs = mergeMSAs ? new Dictionary() : null; m_stemNames = new Dictionary(); m_mprFeatures = new Dictionary(); @@ -674,6 +674,10 @@ protected virtual void LoadLexEntries(Stratum stratum, ILexEntry entry, IList())) { - writer.WriteStartElement("LoadErrors"); - m_language = HCLoader.Load(m_cache, new XmlHCLoadErrorLogger(writer), out m_representativeMSAs); - writer.WriteEndElement(); XElement parserParamsElem = XElement.Parse(m_cache.LanguageProject.MorphologicalDataOA.ParserParameters); XElement delReappsElem = parserParamsElem.Elements("HC").Elements("DelReapps").FirstOrDefault(); XElement guessRootsElem = parserParamsElem.Elements("HC").Elements("GuessRoots").FirstOrDefault(); XElement mergeAnalysesElem = parserParamsElem.Elements("HC").Elements("MergeAnalyses").FirstOrDefault(); + XElement mergeMSAsElem = parserParamsElem.Elements("HC").Elements("MergeMSAs").FirstOrDefault(); XElement maxRootsElem = parserParamsElem.Elements("HC").Elements("MaxRoots").FirstOrDefault(); XElement maxAlternativesElem = parserParamsElem.Elements("HC").Elements("MaxAlternatives").FirstOrDefault(); if (delReappsElem != null) @@ -171,11 +170,23 @@ private void LoadParser() if (guessRootsElem != null) m_guessRoots = (bool) guessRootsElem; if (mergeAnalysesElem != null) - m_mergeAnalyses = (bool) mergeAnalysesElem; + m_mergeAnalyses = (bool)mergeAnalysesElem; + if (mergeMSAsElem != null) + m_mergeMSAs = (bool)mergeMSAsElem; if (maxRootsElem != null) maxStemCount = int.Parse(maxRootsElem.Value); if (maxAlternativesElem != null) maxAlternatives = int.Parse(maxAlternativesElem.Value); + writer.WriteStartElement("LoadErrors"); + if (m_mergeMSAs) + { + m_language = HCLoader.Load(m_cache, new XmlHCLoadErrorLogger(writer), out m_representativeMSAs); + } + else + { + m_language = HCLoader.Load(m_cache, new XmlHCLoadErrorLogger(writer)); + } + writer.WriteEndElement(); } m_morpher = new Morpher(m_traceManager, m_language) { DeletionReapplications = delReapps }; m_morpher.MaxStemCount = maxStemCount; diff --git a/Src/LexText/ParserUI/ParserParametersDlg.cs b/Src/LexText/ParserUI/ParserParametersDlg.cs index 5729da0ca0..2979af9a65 100644 --- a/Src/LexText/ParserUI/ParserParametersDlg.cs +++ b/Src/LexText/ParserUI/ParserParametersDlg.cs @@ -34,6 +34,7 @@ public class ParserParametersDlg : ParserParametersBase private const string AcceptUnspecifiedGraphemes = "AcceptUnspecifiedGraphemes"; private const string GuessRoots = "GuessRoots"; private const string MergeAnalyses = "MergeAnalyses"; + private const string MergeMSAs = "MergeMSAs"; private const string Strata = "Strata"; private const string XAmple = "XAmple"; @@ -282,8 +283,9 @@ public void SetDlgInfo(string title, string parserParameters, ILcmOwningSequence m_dataGrid2.TableStyles[0].GridColumnStyles[2].Width = 130; m_dataGrid2.TableStyles[0].GridColumnStyles[4].Width = 160; m_dataGrid2.TableStyles[0].GridColumnStyles[6].Width = 90; - m_dataGrid2.TableStyles[0].GridColumnStyles[7].Width = 100; - m_dataGrid2.TableStyles[0].GridColumnStyles[8].Width = 400; + m_dataGrid2.TableStyles[0].GridColumnStyles[7].Width = 80; + m_dataGrid2.TableStyles[0].GridColumnStyles[8].Width = 100; + m_dataGrid2.TableStyles[0].GridColumnStyles[9].Width = 400; m_compoundRules = compoundRules; if (m_compoundRules?.Count > 0) @@ -318,6 +320,8 @@ private void LoadParserData(DataSet dsParserParameters) hcElem.Add(new XElement(GuessRoots, true)); if (hcElem.Element(MergeAnalyses) == null) hcElem.Add(new XElement(MergeAnalyses, true)); + if (hcElem.Element(MergeMSAs) == null) + hcElem.Add(new XElement(MergeMSAs, true)); if (hcElem.Element(Strata) == null) hcElem.Add(new XElement(Strata, "")); @@ -372,6 +376,7 @@ private DataTable CreateHCDataTable() tblHC.Columns.Add(AcceptUnspecifiedGraphemes, typeof(bool)); tblHC.Columns.Add(GuessRoots, typeof(bool)); tblHC.Columns.Add(MergeAnalyses, typeof(bool)); + tblHC.Columns.Add(MergeMSAs, typeof(bool)); tblHC.Columns.Add(MaxAlternatives, typeof(int)); tblHC.Columns.Add(Strata, typeof(string)); return tblHC; From edded8e08b811739511cc82ea751ea8a4aadf154 Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Tue, 29 Sep 2026 09:46:20 -0700 Subject: [PATCH 5/8] Fix unit test --- Src/LexText/ParserCore/ParserCoreTests/HCLoaderTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Src/LexText/ParserCore/ParserCoreTests/HCLoaderTests.cs b/Src/LexText/ParserCore/ParserCoreTests/HCLoaderTests.cs index 954fb07194..1e6b426657 100644 --- a/Src/LexText/ParserCore/ParserCoreTests/HCLoaderTests.cs +++ b/Src/LexText/ParserCore/ParserCoreTests/HCLoaderTests.cs @@ -674,7 +674,8 @@ public void RepresentativeMSAs() lexSense.MorphoSyntaxAnalysisRA = msa; msa.PartOfSpeechRA = m_verb; - LoadLanguage(); + m_loadErrors.Clear(); + m_lang = HCLoader.Load(Cache, new TestHCLoadErrorLogger(m_loadErrors), out _); Assert.That(m_lang.Strata[0].MorphologicalRules.Count, Is.EqualTo(1)); var rule = (AffixProcessRule)m_lang.Strata[0].MorphologicalRules[0]; From ca68e83d21e49c0483739f70f8cfc9fdf23a0c53 Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Wed, 30 Sep 2026 08:25:03 -0700 Subject: [PATCH 6/8] Fix bugs reported by John Lambert --- Src/LexText/ParserCore/HCLoader.cs | 25 +++--------- Src/LexText/ParserCore/HCParser.cs | 39 +++++++++++++++---- .../ParserCoreTests/HCLoaderTests.cs | 4 ++ 3 files changed, 40 insertions(+), 28 deletions(-) diff --git a/Src/LexText/ParserCore/HCLoader.cs b/Src/LexText/ParserCore/HCLoader.cs index d2020ab456..41d45dfb34 100644 --- a/Src/LexText/ParserCore/HCLoader.cs +++ b/Src/LexText/ParserCore/HCLoader.cs @@ -649,7 +649,7 @@ protected virtual void LoadLexEntries(Stratum stratum, ILexEntry entry, IList())) + foreach (IMoStemMsa msa in UniqueMSAs(mainEntry.MorphoSyntaxAnalysesOC.OfType())) LoadLexEntryOfVariant(stratum, inflType, msa, allos, entry.ShortName); } else @@ -668,7 +668,7 @@ protected virtual void LoadLexEntries(Stratum stratum, ILexEntry entry, IList())) + foreach (IMoStemMsa msa in UniqueMSAs(entry.MorphoSyntaxAnalysesOC.OfType())) LoadLexEntry(stratum, msa, allos, entry.ShortName); } @@ -694,24 +694,9 @@ IMoMorphSynAnalysis RepresentativeMSA(IMoMorphSynAnalysis msa) return msa; } - IEnumerable UniqueStemMSAs(IEnumerable msas) + private IEnumerable UniqueMSAs(IEnumerable msas) where T : class, IMoMorphSynAnalysis { - HashSet newMsas = new HashSet(); - foreach (var msa in msas) - { - newMsas.Add((IMoStemMsa)RepresentativeMSA(msa)); - } - return newMsas; - } - - IEnumerable UniqueMSAs(IEnumerable msas) - { - HashSet newMsas = new HashSet(); - foreach (var msa in msas) - { - newMsas.Add(RepresentativeMSA(msa)); - } - return newMsas; + return msas.Select(msa => (T)RepresentativeMSA(msa)).Distinct(); } protected IEnumerable GetInflTypes(ILexEntryRef lexEntryRef) @@ -2273,7 +2258,7 @@ private IEnumerable Permute(List> items, int index) private void LoadMorphemeCoOccurrenceRules(IMoMorphAdhocProhib morphAdhocProhib) { List firstMorphemes; - if (m_morphemes.TryGetValue(morphAdhocProhib.FirstMorphemeRA, out firstMorphemes)) + if (m_morphemes.TryGetValue(RepresentativeMSA(morphAdhocProhib.FirstMorphemeRA), out firstMorphemes)) { var allOthers = new List>(); foreach (IMoMorphSynAnalysis msa in UniqueMSAs(morphAdhocProhib.RestOfMorphsRS)) diff --git a/Src/LexText/ParserCore/HCParser.cs b/Src/LexText/ParserCore/HCParser.cs index a5186c67d1..9eb5e5d902 100644 --- a/Src/LexText/ParserCore/HCParser.cs +++ b/Src/LexText/ParserCore/HCParser.cs @@ -2,21 +2,23 @@ // This software is licensed under the LGPL, version 2.1 or later // (http://www.gnu.org/licenses/lgpl-2.1.html) +using SIL.Data; +using SIL.LCModel; +using SIL.LCModel.Infrastructure; +using SIL.Machine.Annotations; +using SIL.Machine.Morphology.HermitCrab; +using SIL.Machine.Morphology.HermitCrab.MorphologicalRules; +using SIL.Machine.Rules; +using SIL.ObjectModel; using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Text; +using System.Web.Caching; using System.Xml; using System.Xml.Linq; -using SIL.LCModel; -using SIL.LCModel.Infrastructure; -using SIL.Machine.Annotations; -using SIL.Machine.Morphology.HermitCrab; -using SIL.Machine.Morphology.HermitCrab.MorphologicalRules; -using SIL.Machine.Rules; -using SIL.ObjectModel; namespace SIL.FieldWorks.WordWorks.Parser { @@ -185,6 +187,7 @@ private void LoadParser() else { m_language = HCLoader.Load(m_cache, new XmlHCLoadErrorLogger(writer)); + m_representativeMSAs = null; } writer.WriteEndElement(); } @@ -204,7 +207,27 @@ private XDocument ParseToXml(string form, bool tracing, IEnumerable selectT { if (selectTraceMorphs != null) { - var selectTraceMorphsSet = new HashSet(selectTraceMorphs); + HashSet selectTraceMorphsSet; + if (m_representativeMSAs == null) + { + selectTraceMorphsSet = new HashSet(selectTraceMorphs); + } + else + { + selectTraceMorphsSet = new HashSet(); + IMoMorphSynAnalysisRepository repository = m_cache.LangProject.Services.GetInstance(); + foreach (int selectTraceMorph in selectTraceMorphs) + { + if (repository.TryGetObject(selectTraceMorph, out IMoMorphSynAnalysis msa) && m_representativeMSAs.ContainsKey(msa)) + { + selectTraceMorphsSet.Add(m_representativeMSAs[msa].Hvo); + } + else + { + selectTraceMorphsSet.Add(selectTraceMorph); + } + } + } m_morpher.LexEntrySelector = entry => selectTraceMorphsSet.Contains((int) entry.Properties[MsaID]); m_morpher.RuleSelector = rule => { diff --git a/Src/LexText/ParserCore/ParserCoreTests/HCLoaderTests.cs b/Src/LexText/ParserCore/ParserCoreTests/HCLoaderTests.cs index 1e6b426657..d22c6a8be7 100644 --- a/Src/LexText/ParserCore/ParserCoreTests/HCLoaderTests.cs +++ b/Src/LexText/ParserCore/ParserCoreTests/HCLoaderTests.cs @@ -683,6 +683,10 @@ public void RepresentativeMSAs() Assert.That(rule.RequiredSyntacticFeatureStruct.ToString(), Is.EqualTo("[POS:V]")); Assert.That(rule.Gloss, Is.EqualTo("gloss")); Assert.That(rule.IsPartial, Is.True); + + m_loadErrors.Clear(); + m_lang = HCLoader.Load(Cache, new TestHCLoadErrorLogger(m_loadErrors)); + Assert.That(m_lang.Strata[0].MorphologicalRules.Count, Is.EqualTo(2)); } [Test] From 73c25ef58607a6097456b1e6ca4b6839072272db Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Thu, 1 Oct 2026 07:38:56 -0700 Subject: [PATCH 7/8] Fix problems reported by Jason, add RepresentativeSAHvo --- Src/LexText/ParserCore/HCLoader.cs | 6 ++++- Src/LexText/ParserCore/HCParser.cs | 41 ++++++++++++++---------------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/Src/LexText/ParserCore/HCLoader.cs b/Src/LexText/ParserCore/HCLoader.cs index 41d45dfb34..035d2a5966 100644 --- a/Src/LexText/ParserCore/HCLoader.cs +++ b/Src/LexText/ParserCore/HCLoader.cs @@ -678,6 +678,10 @@ IMoMorphSynAnalysis RepresentativeMSA(IMoMorphSynAnalysis msa) { return msa; } + if (msa is IMoDerivStepMsa) + { + return msa; + } if (m_representativeMSAs.TryGetValue(msa, out IMoMorphSynAnalysis representativeMSA)) { return representativeMSA; @@ -685,7 +689,7 @@ IMoMorphSynAnalysis RepresentativeMSA(IMoMorphSynAnalysis msa) ILexEntry entry = msa.OwnerOfClass(); foreach (var msa2 in entry.MorphoSyntaxAnalysesOC) { - if (msa2.EqualsMsa(msa) && entry.SenseWithMsa(msa2) != null) + if (msa2.ClassID == msa.ClassID && msa2.EqualsMsa(msa) && entry.SenseWithMsa(msa2) != null) { m_representativeMSAs[msa] = msa2; return msa2; diff --git a/Src/LexText/ParserCore/HCParser.cs b/Src/LexText/ParserCore/HCParser.cs index 9eb5e5d902..3462f37861 100644 --- a/Src/LexText/ParserCore/HCParser.cs +++ b/Src/LexText/ParserCore/HCParser.cs @@ -16,7 +16,6 @@ using System.IO; using System.Linq; using System.Text; -using System.Web.Caching; using System.Xml; using System.Xml.Linq; @@ -207,27 +206,7 @@ private XDocument ParseToXml(string form, bool tracing, IEnumerable selectT { if (selectTraceMorphs != null) { - HashSet selectTraceMorphsSet; - if (m_representativeMSAs == null) - { - selectTraceMorphsSet = new HashSet(selectTraceMorphs); - } - else - { - selectTraceMorphsSet = new HashSet(); - IMoMorphSynAnalysisRepository repository = m_cache.LangProject.Services.GetInstance(); - foreach (int selectTraceMorph in selectTraceMorphs) - { - if (repository.TryGetObject(selectTraceMorph, out IMoMorphSynAnalysis msa) && m_representativeMSAs.ContainsKey(msa)) - { - selectTraceMorphsSet.Add(m_representativeMSAs[msa].Hvo); - } - else - { - selectTraceMorphsSet.Add(selectTraceMorph); - } - } - } + HashSet selectTraceMorphsSet = new HashSet(selectTraceMorphs.Select(hvo => RepresentativeMSAHvo(hvo)).Distinct()); m_morpher.LexEntrySelector = entry => selectTraceMorphsSet.Contains((int) entry.Properties[MsaID]); m_morpher.RuleSelector = rule => { @@ -269,6 +248,24 @@ private XDocument ParseToXml(string form, bool tracing, IEnumerable selectT return doc; } + private int RepresentativeMSAHvo(int hvo) + { + if (m_representativeMSAs == null) + { + return hvo; + } + IMoMorphSynAnalysisRepository repository = m_cache.LangProject.Services.GetInstance(); + if (repository.TryGetObject(hvo, out IMoMorphSynAnalysis msa) && m_representativeMSAs.ContainsKey(msa)) + { + return m_representativeMSAs[msa].Hvo; + } + else + { + return hvo; + } + + } + /// /// Check integrity of phoneme-based natural classes (PhNCSegments) /// when there are phonological features From d200abc70a9042697ba6d3fa5b4d574c66dadbe3 Mon Sep 17 00:00:00 2001 From: John Maxwell Date: Thu, 1 Oct 2026 13:43:27 -0700 Subject: [PATCH 8/8] Add acceptance test requested by John Lambert --- Src/LexText/ParserCore/ParserCoreTests/HCLoaderTests.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Src/LexText/ParserCore/ParserCoreTests/HCLoaderTests.cs b/Src/LexText/ParserCore/ParserCoreTests/HCLoaderTests.cs index d22c6a8be7..bc63c44c5f 100644 --- a/Src/LexText/ParserCore/ParserCoreTests/HCLoaderTests.cs +++ b/Src/LexText/ParserCore/ParserCoreTests/HCLoaderTests.cs @@ -674,9 +674,17 @@ public void RepresentativeMSAs() lexSense.MorphoSyntaxAnalysisRA = msa; msa.PartOfSpeechRA = m_verb; + ILexEntry stemEntry = AddEntry(MoMorphTypeTags.kguidMorphStem, "a", "gloss", new SandboxGenericMSA { MsaType = MsaType.kStem }); + var stemSense = senseFactory.Create(); + stemEntry.SensesOS.Add(stemSense); + IMoStemMsa stemMsa = Cache.ServiceLocator.GetInstance().Create(); + stemEntry.MorphoSyntaxAnalysesOC.Add(stemMsa); + stemSense.MorphoSyntaxAnalysisRA = stemMsa; + m_loadErrors.Clear(); m_lang = HCLoader.Load(Cache, new TestHCLoadErrorLogger(m_loadErrors), out _); + Assert.That(m_lang.Strata[0].Entries.Count, Is.EqualTo(1)); Assert.That(m_lang.Strata[0].MorphologicalRules.Count, Is.EqualTo(1)); var rule = (AffixProcessRule)m_lang.Strata[0].MorphologicalRules[0]; @@ -686,6 +694,7 @@ public void RepresentativeMSAs() m_loadErrors.Clear(); m_lang = HCLoader.Load(Cache, new TestHCLoadErrorLogger(m_loadErrors)); + Assert.That(m_lang.Strata[0].Entries.Count, Is.EqualTo(2)); Assert.That(m_lang.Strata[0].MorphologicalRules.Count, Is.EqualTo(2)); }