From 73cf047c50a3e424597844d740a319cc2cfcb210 Mon Sep 17 00:00:00 2001 From: Hasso Pape Date: Fri, 25 Mar 2022 18:07:39 -0500 Subject: [PATCH] [API] LT-8457: Sort References Properly * Add IStTxtPara.ReferenceForSorting(ISegment, int) to compute a computer-sortable reference string Part of https://jira.sil.org/browse/LT-8457 --- src/SIL.LCModel/DomainImpl/ScrTxtPara.cs | 7 +++ src/SIL.LCModel/DomainImpl/StTxtPara.cs | 47 +++++++++++++++++++ src/SIL.LCModel/InterfaceAdditions.cs | 22 ++++++--- .../DomainImpl/ScrTxtParaTests.cs | 32 +++++++++++++ .../DomainImpl/StTxtParaTests.cs | 27 +++++++++++ 5 files changed, 129 insertions(+), 6 deletions(-) diff --git a/src/SIL.LCModel/DomainImpl/ScrTxtPara.cs b/src/SIL.LCModel/DomainImpl/ScrTxtPara.cs index ac109252..2030abaa 100644 --- a/src/SIL.LCModel/DomainImpl/ScrTxtPara.cs +++ b/src/SIL.LCModel/DomainImpl/ScrTxtPara.cs @@ -2136,6 +2136,13 @@ public override ITsString Reference(ISegment seg, int ich) } return Cache.MakeUserTss("unknown"); // should never happen, I think? } + + /// + public override ITsString ReferenceForSorting(ISegment seg, int ich) + { + throw new NotImplementedException(); + } + /// ------------------------------------------------------------------------------------ /// /// Gets the footnote sequence. diff --git a/src/SIL.LCModel/DomainImpl/StTxtPara.cs b/src/SIL.LCModel/DomainImpl/StTxtPara.cs index 63e944ea..fbca9744 100644 --- a/src/SIL.LCModel/DomainImpl/StTxtPara.cs +++ b/src/SIL.LCModel/DomainImpl/StTxtPara.cs @@ -117,6 +117,53 @@ public virtual ITsString Reference(ISegment seg, int ich) return bldr.GetString(); } + /// + public virtual ITsString ReferenceForSorting(ISegment seg, int ich) + { + if (!(Owner is IStText stText)) + { + return TsStringUtils.EmptyString(Cache.DefaultUserWs); + } + + ITsString tssName = null; + // TODO: Abbr + //var fUsingAbbr = false; + //if (stText.Owner is IText text) + //{ + // tssName = text.Abbreviation.BestVernacularAnalysisAlternative; + // if (!TsStringUtils.IsNullOrEmpty(tssName)) + // { + // fUsingAbbr = true; + // } + //} + tssName = stText.Title.BestVernacularAnalysisAlternative; + var bldr = tssName.GetBldr(); + // TODO: Props? + var props = bldr.get_Properties(0); + // TODO: *** check? + + bldr.Append(" ", props); + + // Insert paragraph number. + int iPara = stText.ParagraphsOS.IndexOf(this) + 1; + bldr.Replace(bldr.Length, bldr.Length, iPara.ToString(), props); + + // And a period... + bldr.Replace(bldr.Length, bldr.Length, ".", props); + + // And now the segment number + int iseg = SegmentsOS.IndexOf(seg) + 1; + bldr.Replace(bldr.Length, bldr.Length, iseg.ToString(), props); + return bldr.GetString(); + } + + /// Pads the given int with zeroes to the max length of an int + protected internal static string ZeroPadForStringComparison(int i) + { + // because int.MaxValue.ToString().Length is 10 + return i.ToString("D10"); + } + /// ------------------------------------------------------------------------------------ /// /// Finds the ORC of the specified picture and deletes it from the paragraph and any diff --git a/src/SIL.LCModel/InterfaceAdditions.cs b/src/SIL.LCModel/InterfaceAdditions.cs index cdedf74e..49bed223 100644 --- a/src/SIL.LCModel/InterfaceAdditions.cs +++ b/src/SIL.LCModel/InterfaceAdditions.cs @@ -3136,15 +3136,25 @@ IStTxtPara PreviousParagraph List GetChartCellRefs(); /// ------------------------------------------------------------------------------------ - /// - /// Return a Reference (e.g., Scripture reference, or text abbreviation/para #/sentence#) for the specified character - /// position (in the whole paragraph), which is assumed to belong to the specified segment. - /// (For now, ich is not actually used, but it may become important if we decide not to split segements for - /// verse numbers.) - /// + /// + /// Return a Reference (e.g., Scripture reference, or text abbreviation+para #+sentence #) for the specified character + /// position (in the whole paragraph), which is assumed to belong to the specified segment. + /// (For now, ich is not actually used, but it may become important if we decide not to split segments for + /// verse numbers.) + /// /// ------------------------------------------------------------------------------------ ITsString Reference(ISegment seg, int ich); + /// ------------------------------------------------------------------------------------ + /// + /// Return a Reference (e.g., Scripture reference, or text abbreviation+para #+sentence #) for the specified character + /// position (in the whole paragraph), which is assumed to belong to the specified segment. + /// To allow greater accuracy and precision in sorting, numbers are zero-padded to the length of and ich + /// is included at the end. + /// + /// ------------------------------------------------------------------------------------ + ITsString ReferenceForSorting(ISegment seg, int ich); + /// ------------------------------------------------------------------------------------ /// /// Splits the paragraph at the specified character index. diff --git a/tests/SIL.LCModel.Tests/DomainImpl/ScrTxtParaTests.cs b/tests/SIL.LCModel.Tests/DomainImpl/ScrTxtParaTests.cs index d75393fc..d25e5678 100644 --- a/tests/SIL.LCModel.Tests/DomainImpl/ScrTxtParaTests.cs +++ b/tests/SIL.LCModel.Tests/DomainImpl/ScrTxtParaTests.cs @@ -567,6 +567,38 @@ public void Reference() // segments in the previous section. Assert.That(para3.Reference(v2seg4, v2seg4.BeginOffset + 1).Text, Is.EqualTo("MAT 1:2")); } + + /// + /// Test the ReferenceForSorting method for Scripture paragraphs. + /// + [Test] + public void ReferenceForSorting() + { + AddDataToMatthew(); + var para1 = (IStTxtPara) m_book.SectionsOS[1].ContentOA.ParagraphsOS[0]; // Actually ScrTxtPara + var seg = para1.SegmentsOS[1]; // first content ref, after the chapter and verse number stuff. + Assert.That(para1.ReferenceForSorting(seg, seg.BeginOffset + 1).Text, Is.EqualTo("41_MAT 0000000001:0000000001")); + AddRunToMockedPara(para1, "Verse two second sentence.", null); + var v2seg1 = para1.SegmentsOS[3]; // first segment of two-sentence verse + Assert.That(para1.ReferenceForSorting(v2seg1, v2seg1.BeginOffset + 1).Text, Is.EqualTo("41_MAT 0000000001:0000000002a")); + var v2seg2 = para1.SegmentsOS[4]; // first segment of two-sentence verse + Assert.That(para1.ReferenceForSorting(v2seg2, v2seg2.BeginOffset + 1).Text, Is.EqualTo("41_MAT 0000000001:0000000002b")); + IStTxtPara para2 = AddParaToMockedSectionContent((IScrSection)para1.Owner.Owner, ScrStyleNames.NormalParagraph); + AddRunToMockedPara(para2, "Verse 2 seg 3", null); + var v2seg3 = para2.SegmentsOS[0]; // third segment of three-sentence verse split over two paragraphs. + Assert.That(para2.ReferenceForSorting(v2seg3, v2seg3.BeginOffset + 1).Text, Is.EqualTo("41_MAT 0000000001:0000000002c")); + var newSection = AddSectionToMockedBook(m_book); + IStTxtPara para3 = AddParaToMockedSectionContent(newSection, ScrStyleNames.NormalParagraph); + AddRunToMockedPara(para3, "Verse 2 seg 4", null); + var v2seg4 = para3.SegmentsOS[0]; // fourth segment of four-sentence verse split over two sections(!). + // JohnT: arguably this should give 41_MAT 0000000001:0000000002d. The current implementation does not detect the + // segments in the previous section. + Assert.That(para3.ReferenceForSorting(v2seg4, v2seg4.BeginOffset + 1).Text, Is.EqualTo("41_MAT 0000000001:0000000002")); + + var scrBook1Samuel = CreateBookData(9, "1 Samuel"); + var scrBookSusanna = CreateBookData(76, "Susanna"); + // TODO (Hasso) 2022.03: Enoch or some other >100 book + } #endregion #region Moving paragraphs between books tests diff --git a/tests/SIL.LCModel.Tests/DomainImpl/StTxtParaTests.cs b/tests/SIL.LCModel.Tests/DomainImpl/StTxtParaTests.cs index ca62f44e..62b5941c 100644 --- a/tests/SIL.LCModel.Tests/DomainImpl/StTxtParaTests.cs +++ b/tests/SIL.LCModel.Tests/DomainImpl/StTxtParaTests.cs @@ -35,6 +35,33 @@ protected override void CreateTestData() } #endregion + #region ReferenceForSorting method tests + [Test] + public void ReferenceForSorting() + { + var para1 = AddParaToMockedText(m_stText, null); + AddRunToMockedPara(para1, "This text is indexed. It is also segmented.", null); + + var para2 = AddParaToMockedText(m_stText, null); + AddRunToMockedPara(para2, "This is the second paragraph. It is runny. It has three sentences.", null); + + // SUT + var result = para1.ReferenceForSorting(para1.SegmentsOS[0], 10); + Assert.That(result, Is.EqualTo("My Interlinear Text 0000000001.0000000001 0000000010")); + result = para1.ReferenceForSorting(para1.SegmentsOS[1], 25); + Assert.That(result, Is.EqualTo("My Interlinear Text 0000000001.0000000002 0000000025")); + result = para1.ReferenceForSorting(para2.SegmentsOS[0], 5); + Assert.That(result, Is.EqualTo("My Interlinear Text 0000000002.0000000001 0000000005")); + } + + [TestCase(0, ExpectedResult = "0000000000")] + [TestCase(1, ExpectedResult = "0000000001")] + [TestCase(12, ExpectedResult = "0000000012")] + [TestCase(512, ExpectedResult = "0000000512")] + [TestCase(int.MaxValue, ExpectedResult = "2147483647")] + public string ZeroPadForStringComparison(int i) => StTxtPara.ZeroPadForStringComparison(i); + #endregion ReferenceForSorting method tests + #region ReplaceTextRange method tests ///-------------------------------------------------------------------------------------- ///