diff --git a/Src/LexText/ParserCore/ParserCoreTests/ParseFilerProcessingTests.cs b/Src/LexText/ParserCore/ParserCoreTests/ParseFilerProcessingTests.cs index 0237fb41d2..937d4ce30c 100644 --- a/Src/LexText/ParserCore/ParserCoreTests/ParseFilerProcessingTests.cs +++ b/Src/LexText/ParserCore/ParserCoreTests/ParseFilerProcessingTests.cs @@ -13,6 +13,8 @@ using System; using System.Linq; +using System.Reflection; +using System.Xml.Linq; using NUnit.Framework; using SIL.FieldWorks.Common.FwUtils; using SIL.LCModel.Core.Text; @@ -99,6 +101,37 @@ protected void ExecuteIdleQueue() m_idleQueue.Clear(); } + /// + /// Creates a stem entry with two senses, each with its own MoStemMsa, so tests can + /// verify that a specific MSA (not just "the first one") gets resolved. + /// + private ILexEntry CreateStemEntryWithTwoMsas(string form, out IMoStemMsa firstMsa, out IMoStemMsa secondMsa) + { + ILexEntry entry = m_entryFactory.Create(); + IMoStemAllomorph entryForm = m_stemAlloFactory.Create(); + entry.AlternateFormsOS.Add(entryForm); + entryForm.Form.VernacularDefaultWritingSystem = TsStringUtils.MakeString(form, m_vernacularWS.Handle); + firstMsa = m_stemMsaFactory.Create(); + entry.MorphoSyntaxAnalysesOC.Add(firstMsa); + ILexSense firstSense = m_senseFactory.Create(); + entry.SensesOS.Add(firstSense); + firstSense.MorphoSyntaxAnalysisRA = firstMsa; + secondMsa = m_stemMsaFactory.Create(); + entry.MorphoSyntaxAnalysesOC.Add(secondMsa); + ILexSense secondSense = m_senseFactory.Create(); + entry.SensesOS.Add(secondSense); + secondSense.MorphoSyntaxAnalysisRA = secondMsa; + return entry; + } + + private static ParseMorph InvokeTryCreateParseMorph(LcmCache cache, XElement morphElem, out bool succeeded) + { + MethodInfo method = typeof(XAmpleParser).GetMethod("TryCreateParseMorph", BindingFlags.NonPublic | BindingFlags.Static); + var args = new object[] { cache, morphElem, null }; + succeeded = (bool) method.Invoke(null, args); + return (ParseMorph) args[2]; + } + #endregion // Non-tests #region Setup and TearDown @@ -522,6 +555,118 @@ public void LexEntryInflTypeTwoAnalyses() } } + /// ------------------------------------------------------------------------------------ + /// + /// LT-22563: XAmple's MSI DbRef for an irregularly inflected variant is + /// "lexEntryHvo.refIndex.msaHvo" (see LT-22422). TryCreateParseMorph must resolve the + /// specific msaHvo from that DbRef; it must not fall back to just the variant's + /// main/first sense, since that can be a different MSA than XAmple actually chose + /// when the underlying entry has more than one MSA. + /// + /// ------------------------------------------------------------------------------------ + [Test] + public void TryCreateParseMorph_IrregularVariantWithMultipleMsas_UsesMsaFromDbRef() + { + ILexEntry crebV = null; + IMoStemAllomorph crebVForm = null; + IMoStemMsa firstMsa = null; + IMoStemMsa secondMsa = null; + + UndoableUnitOfWorkHelper.Do("Undo stuff", "Redo stuff", m_actionHandler, () => + { + // 'seek' has two senses/MSAs; 'creb' is an irregularly inflected variant of it. + ILexEntryInflType inflType = m_lexEntryInflTypeFactory.Create(); + Cache.LangProject.LexDbOA.VariantEntryTypesOA.PossibilitiesOS.Add(inflType); + + ILexEntry seekV = CreateStemEntryWithTwoMsas("seekVTEST", out firstMsa, out secondMsa); + + crebV = m_entryFactory.Create(); + crebVForm = m_stemAlloFactory.Create(); + crebV.AlternateFormsOS.Add(crebVForm); + crebVForm.Form.VernacularDefaultWritingSystem = TsStringUtils.MakeString("crebVTEST", m_vernacularWS.Handle); + ILexEntryRef lexEntryRef = m_lexEntryRefFactory.Create(); + crebV.EntryRefsOS.Add(lexEntryRef); + lexEntryRef.ComponentLexemesRS.Add(seekV); + lexEntryRef.VariantEntryTypesRS.Add(inflType); + }); + + // Mimic what XAmple echoes back for this kind of variant: MoForm DbRef is the + // allomorph hvo, and MSI DbRef is "lexEntryHvo.refIndex.msaHvo", pointing + // specifically at the *second* MSA of 'seek'. + var morphElem = new XElement("Morph", + new XElement("MoForm", new XAttribute("DbRef", crebVForm.Hvo)), + new XElement("MSI", new XAttribute("DbRef", string.Format("{0}.0.{1}", crebV.Hvo, secondMsa.Hvo)))); + + bool succeeded; + ParseMorph morph = InvokeTryCreateParseMorph(Cache, morphElem, out succeeded); + + Assert.That(succeeded, Is.True); + Assert.That(morph, Is.Not.Null); + Assert.That(morph.Msa.Hvo, Is.EqualTo(secondMsa.Hvo), + "Should use the MSA hvo encoded in the MSI DbRef, not just the variant's first/main sense"); + Assert.That(morph.Msa.Hvo, Is.Not.EqualTo(firstMsa.Hvo)); + } + + /// ------------------------------------------------------------------------------------ + /// + /// LT-22563: hardens the above test by giving the variant *two* EntryRefsOS (pointing + /// at two different base entries, each itself with two MSAs) and checking both possible + /// refIndex values. This guards both halves of the "lexEntryHvo.refIndex.msaHvo" DbRef: + /// picking the right LexEntryRef by index, and then the right MSA within that ref's + /// target entry -- not just the case where there is only one ref to pick from. + /// + /// ------------------------------------------------------------------------------------ + [TestCase(0)] + [TestCase(1)] + public void TryCreateParseMorph_IrregularVariantWithMultipleEntryRefsAndMsas_UsesRefIndexAndMsaFromDbRef(int entryRefIndex) + { + ILexEntry crebV = null; + IMoStemAllomorph crebVForm = null; + IMoStemMsa believeSecondMsa = null; + IMoStemMsa seekSecondMsa = null; + + UndoableUnitOfWorkHelper.Do("Undo stuff", "Redo stuff", m_actionHandler, () => + { + ILexEntryInflType inflType = m_lexEntryInflTypeFactory.Create(); + Cache.LangProject.LexDbOA.VariantEntryTypesOA.PossibilitiesOS.Add(inflType); + + IMoStemMsa believeFirstMsa; + ILexEntry believeV = CreateStemEntryWithTwoMsas("believeVTEST2", out believeFirstMsa, out believeSecondMsa); + IMoStemMsa seekFirstMsa; + ILexEntry seekV = CreateStemEntryWithTwoMsas("seekVTEST2", out seekFirstMsa, out seekSecondMsa); + + crebV = m_entryFactory.Create(); + crebVForm = m_stemAlloFactory.Create(); + crebV.AlternateFormsOS.Add(crebVForm); + crebVForm.Form.VernacularDefaultWritingSystem = TsStringUtils.MakeString("crebVTEST2", m_vernacularWS.Handle); + + // EntryRefsOS[0] -> believeV, EntryRefsOS[1] -> seekV. + ILexEntryRef believeRef = m_lexEntryRefFactory.Create(); + crebV.EntryRefsOS.Add(believeRef); + believeRef.ComponentLexemesRS.Add(believeV); + believeRef.VariantEntryTypesRS.Add(inflType); + + ILexEntryRef seekRef = m_lexEntryRefFactory.Create(); + crebV.EntryRefsOS.Add(seekRef); + seekRef.ComponentLexemesRS.Add(seekV); + seekRef.VariantEntryTypesRS.Add(inflType); + }); + + IMoStemMsa expectedMsa = entryRefIndex == 0 ? believeSecondMsa : seekSecondMsa; + + var morphElem = new XElement("Morph", + new XElement("MoForm", new XAttribute("DbRef", crebVForm.Hvo)), + new XElement("MSI", new XAttribute("DbRef", string.Format("{0}.{1}.{2}", crebV.Hvo, entryRefIndex, expectedMsa.Hvo)))); + + bool succeeded; + ParseMorph morph = InvokeTryCreateParseMorph(Cache, morphElem, out succeeded); + + Assert.That(succeeded, Is.True); + Assert.That(morph, Is.Not.Null); + Assert.That(morph.Msa.Hvo, Is.EqualTo(expectedMsa.Hvo), + string.Format("Should resolve EntryRefsOS[{0}] and the specific msa hvo encoded in the DbRef", entryRefIndex)); + } + [Test] public void LexEntryInflTypeAnalysisWithNullForSlotFiller() { diff --git a/Src/LexText/ParserCore/ParserXmlWriterExtensions.cs b/Src/LexText/ParserCore/ParserXmlWriterExtensions.cs index 9172e2ed94..9cd0511f5f 100644 --- a/Src/LexText/ParserCore/ParserXmlWriterExtensions.cs +++ b/Src/LexText/ParserCore/ParserXmlWriterExtensions.cs @@ -18,7 +18,7 @@ namespace SIL.FieldWorks.WordWorks.Parser { internal static class ParserXmlWriterExtensions { - private static Tuple ProcessMsaHvo(string msaHvo) + public static Tuple ProcessMsaHvo(string msaHvo) { string[] msaHvoParts = msaHvo.Split('.'); // the msa hvo has one part or three parts separated by a period. diff --git a/Src/LexText/ParserCore/XAmpleParser.cs b/Src/LexText/ParserCore/XAmpleParser.cs index df7d2e0150..57bde6cbe2 100644 --- a/Src/LexText/ParserCore/XAmpleParser.cs +++ b/Src/LexText/ParserCore/XAmpleParser.cs @@ -245,9 +245,11 @@ private static bool TryCreateParseMorph(LcmCache cache, XElement morphElem, out // required slots in affix templates. The parser filer can ignore these. // 3. ().TryGetObject(int.Parse(formHvo), out objForm)) { @@ -261,8 +263,9 @@ private static bool TryCreateParseMorph(LcmCache cache, XElement morphElem, out return true; } - // Irregulary inflected forms can have a combination MSA hvo: the LexEntry hvo, a period, and an index to the LexEntryRef - Tuple msaTuple = ProcessMsaHvo(msaHvo); + // Irregulary inflected forms can have a combination MSA hvo: the LexEntry hvo, a period, an index to the LexEntryRef, + // another period and the MSA hvo + Tuple msaTuple = ParserXmlWriterExtensions.ProcessMsaHvo(msaHvo); ICmObject objMsa; if (!cache.ServiceLocator.GetInstance().TryGetObject(msaTuple.Item1, out objMsa)) { @@ -286,10 +289,19 @@ private static bool TryCreateParseMorph(LcmCache cache, XElement morphElem, out ILexEntryRef lexEntryRef = msaAsLexEntry.EntryRefsOS[msaTuple.Item2]; if (lexEntryRef != null && lexEntryRef.ComponentLexemesRS.Count() > 0) { - // make sure there is at least one component lexeme (LT-22328) - ILexSense sense = MorphServices.GetMainOrFirstSenseOfVariant(lexEntryRef); var inflType = lexEntryRef.VariantEntryTypesRS[0] as ILexEntryInflType; - morph = new ParseMorph(form, sense.MorphoSyntaxAnalysisRA, inflType); + IMoStemMsa stemMsa = (IMoStemMsa)cache.ServiceLocator.GetInstance().GetObject(msaTuple.Item3); + if (stemMsa != null) + { + // use the msa itself + morph = new ParseMorph(form, stemMsa, inflType); + } + else + { + // make sure there is at least one component lexeme (LT-22328) + ILexSense sense = MorphServices.GetMainOrFirstSenseOfVariant(lexEntryRef); + morph = new ParseMorph(form, sense.MorphoSyntaxAnalysisRA, inflType); + } return true; } } @@ -300,12 +312,6 @@ private static bool TryCreateParseMorph(LcmCache cache, XElement morphElem, out return true; } - private static Tuple ProcessMsaHvo(string msaHvo) - { - string[] msaHvoParts = msaHvo.Split('.'); - return Tuple.Create(int.Parse(msaHvoParts[0]), msaHvoParts.Length == 2 ? int.Parse(msaHvoParts[1]) : 0); - } - public XDocument ParseWordXml(string word) { CheckDisposed();