diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f32b37ca..d3bec4397 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - [SIL.DblBundle] `TextBundle.GetFonts` (to replace deprecated `CopyFontFiles`) - [SIL.DblBundle] `TextBundle.GetLdml` (to replace deprecated `CopyLdmlFile`) - [SIL.Scripture] `ScrVers.Save` overload to allow serialization to a `TextWriter`. +- [SIL.Scripture] `VerseRef.TrySetVerseUnicode` to set 'verse' and 'verseRef' variables with non-Roman numerals. - [SIL.Core] `XmlSerializationHelper.Serialize` to allow serialization to a `TextWriter`. - [SIL.Core] `XmlSerializationHelper.Deserialize` to allow deserialization from a `TextReader`. - [SIL.Core] `Platform.IsGnomeShell` to detect if executing in a Gnome Shell diff --git a/SIL.Scripture.Tests/VerseRefTests.cs b/SIL.Scripture.Tests/VerseRefTests.cs index 108f26b3f..dc072c781 100644 --- a/SIL.Scripture.Tests/VerseRefTests.cs +++ b/SIL.Scripture.Tests/VerseRefTests.cs @@ -2069,7 +2069,7 @@ public void UnBridge() } /// - /// Tests the Verse property's set method with numerals from various Unicode-supported scripts + /// Tests the TrySetVerseUnicode method with numerals from various Unicode-supported scripts /// [TestCase("५", ExpectedResult = 5, TestName = "Devanagari numeral")] [TestCase("૧૬", ExpectedResult = 16, TestName = "Gujarati numeral")] @@ -2077,11 +2077,38 @@ public void UnBridge() [TestCase("᠔", ExpectedResult = 4, TestName = "Mongolian numeral")] [TestCase("A", ExpectedResult = -1, TestName = "Latin non-numeral")] [TestCase("ะ", ExpectedResult = -1, TestName = "Thai non-numeral")] - public int SetVerse_InterpretUnicodeNumerals(string verseStr) + [TestCase("᠔-᠔", ExpectedResult = 4, TestName = "Mongolian complex verse")] + [TestCase("᠔ᠠ", ExpectedResult = 4, TestName = "Mongolian complex verse - lettered")] + [TestCase("二十", ExpectedResult = 20, TestName = "Japanese numeral", IgnoreReason = "Non-decimal numeral systems not yet implemented. (See issue #1000.)")] + [TestCase("יא", ExpectedResult = 11, TestName = "Hebrew numeral", IgnoreReason = "Non-decimal numeral systems not yet implemented. (See issue #1000.)")] + [TestCase("\U0001113A\U00011138", ExpectedResult = 42, TestName = "Chakma numeral", IgnoreReason = "Surrogate pair handling not yet implemented. (See issue #1000.)")] + public int TrySetVerseUnicode_InterpretNumerals(string verseStr) + { + VerseRef vref = new VerseRef("EXO 6:1"); + + bool success = vref.TrySetVerseUnicode(verseStr); + Assert.AreEqual(success, vref.VerseNum != -1); + + return vref.VerseNum; + } + + /// + /// Tests the Verse property's set method with various input strings + /// + [TestCase("5", ExpectedResult = 5, TestName = "Latin numeral")] + [TestCase("524", ExpectedResult = 524, TestName = "Large Latin numeral")] + [TestCase("A", ExpectedResult = -1, TestName = "Latin non-numeral")] + [TestCase("૧૬", ExpectedResult = -1, TestName = "Non-Latin numeral")] + [TestCase("1-", ExpectedResult = 1, TestName = "Complex verse - incomplete")] + [TestCase("2.3", ExpectedResult = 2, TestName = "Complex verse - decimal")] + [TestCase("5-7", ExpectedResult = 5, TestName = "Complex verse - complete")] + [TestCase("7a", ExpectedResult = 7, TestName = "Complex verse - lettered")] + public int SetVerse_InterpretNumerals(string verseStr) { VerseRef vref = new VerseRef("EXO 6:1"); vref.Verse = verseStr; + return vref.VerseNum; } diff --git a/SIL.Scripture/VerseRef.cs b/SIL.Scripture/VerseRef.cs index ca6d286d3..2600fa2ef 100644 --- a/SIL.Scripture/VerseRef.cs +++ b/SIL.Scripture/VerseRef.cs @@ -242,18 +242,8 @@ public string Chapter [XmlIgnore] public string Verse { - get { return verse ?? (IsDefault || verseNum < 0 ? string.Empty : verseNum.ToString()); } - set - { - short vNum; - verse = !TryGetVerseNum(value, out vNum) ? value.Replace(rtlMark, "") : null; - verseNum = vNum; - if (verseNum >= 0) - return; - - Trace.TraceWarning("Just failed to parse a verse number: " + value); - TryGetVerseNum(verse, out verseNum); - } + get => verse ?? (IsDefault || verseNum < 0 ? string.Empty : verseNum.ToString()); + set => TrySetVerse(value, true); // The USX standard only expects support for Latin numerals {0-9}* in verse numbers. } /// @@ -284,13 +274,29 @@ public string Text } } + /// + /// Tries to set verse and verseNum by parsing the `value` string. + /// This is used by Verse.set and TrySetVerseUnicode + /// + /// true if the verse was set successfully + bool TrySetVerse(string value, bool romanOnly) + { + verse = !TryGetVerseNum(value, romanOnly, out verseNum) ? value.Replace(rtlMark, "") : null; + if (verseNum >= 0) + return true; + + Trace.TraceWarning("Just failed to parse a verse number: " + value); + TryGetVerseNum(verse, romanOnly, out verseNum); + return false; + } + /// /// Parses a verse string and gets the leading numeric portion as a number. /// /// true if the entire string could be parsed as a single, /// simple verse number (1-999); false if the verse string represented /// a verse bridge, contained segment letters, or was invalid - static bool TryGetVerseNum(string verseStr, out short vNum) + static bool TryGetVerseNum(string verseStr, bool romanOnly , out short vNum) { if (string.IsNullOrEmpty(verseStr)) { @@ -302,14 +308,14 @@ static bool TryGetVerseNum(string verseStr, out short vNum) for (int i = 0; i < verseStr.Length; i++) { char ch = verseStr[i]; - if (!char.IsDigit(ch)) + if (romanOnly ? (ch < '0' || ch > '9') : !char.IsDigit(ch)) { if (i == 0) vNum = -1; return false; } - vNum = (short)(vNum * 10 + char.GetNumericValue(ch)); + vNum = (short)(vNum * 10 + (romanOnly ? ch - '0' : char.GetNumericValue(ch))); if (vNum > bcvMaxValue) { // whoops, we got too big! @@ -1338,6 +1344,18 @@ static void ParseVerseNumber(string vNum, out int number, out string segment) segment = vNum.Substring(j); } + /// + /// Parses a verse string and gets the leading numeric portion as a number. + /// Functionally identical to Verse.Set for Roman numbers, made distinct to preserve USX standard. + /// + /// true if the entire string could be parsed as a single, + /// simple verse number in any supported script; false if the verse string represented + /// a verse bridge, contained segment letters, or was invalid + public bool TrySetVerseUnicode(string value) + { + return TrySetVerse(value, false); + } + /// /// Returns whether any of the specified references overlap with this one ///