Skip to content

Commit d7dc83f

Browse files
committed
SF-3191 Improve latest draft retrieval when webhook fails
1 parent e8512c1 commit d7dc83f

File tree

1 file changed

+160
-0
lines changed

1 file changed

+160
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text.RegularExpressions;
5+
using SIL.Scripture;
6+
7+
/// <summary>
8+
/// This is a helper class that will assist in parsing various scripture range formats that are accepted by Serval.
9+
/// </summary>
10+
/// <remarks>
11+
/// Further information can be found in the Serval documentation: <see cref="https://github.com/sillsdev/serval/wiki/Filtering-Paratext-Project-Data-with-a-Scripture-Range/" />
12+
/// This uses the same regexes as SIL.Machine to verify the type of book range.
13+
/// Our specific use case for parsing scripture ranges is different from SIL.Machine and therefore requires it's own implementation.
14+
/// </remarks>
15+
public class ScriptureRangeParser
16+
{
17+
private HashSet<string> Books = [];
18+
private HashSet<string> BooksToRemove = [];
19+
private readonly Regex CommaSeparatedBooks = new Regex(
20+
@"^([A-Z\d]{3}|OT|NT)(, ?([A-Z\d]{3}|OT|NT))*$",
21+
RegexOptions.Compiled
22+
);
23+
24+
private readonly Regex SemiColonSeparatedBooks = new Regex(
25+
@"^([A-Z\d]{3}|OT|NT)(; ?([A-Z\d]{3}|OT|NT))*$",
26+
RegexOptions.Compiled
27+
);
28+
29+
private readonly Regex BookRange = new Regex(@"^-?[A-Z\d]{3}-[A-Z\d]{3}$", RegexOptions.Compiled);
30+
31+
public IEnumerable<string> ParseScriptureRange(string scriptureRange)
32+
{
33+
if (string.IsNullOrWhiteSpace(scriptureRange))
34+
{
35+
return new HashSet<string>();
36+
}
37+
38+
try
39+
{
40+
// Allow a single book as a range
41+
if (scriptureRange.Length == 3)
42+
{
43+
Books.Add(scriptureRange);
44+
}
45+
// Allow semi-colon separated HashSet
46+
else if (SemiColonSeparatedBooks.IsMatch(scriptureRange))
47+
{
48+
var booksHashSet = scriptureRange.Split([';'], StringSplitOptions.RemoveEmptyEntries);
49+
foreach (var book in booksHashSet)
50+
{
51+
ProcessBook(book.ToUpperInvariant());
52+
}
53+
}
54+
// Allow comma separated HashSet
55+
else if (CommaSeparatedBooks.IsMatch(scriptureRange))
56+
{
57+
var booksHashSet = scriptureRange.Split([','], StringSplitOptions.RemoveEmptyEntries);
58+
foreach (var book in booksHashSet)
59+
{
60+
ProcessBook(book.ToUpperInvariant());
61+
}
62+
}
63+
}
64+
catch (Exception ex)
65+
{
66+
// Handle any exceptions that may occur during parsing
67+
Console.WriteLine($"Error parsing scripture range: {ex.Message}");
68+
}
69+
70+
// Remove any books that are marked for removal
71+
foreach (var book in BooksToRemove)
72+
{
73+
if (Books.Contains(book))
74+
{
75+
Books.Remove(book);
76+
}
77+
}
78+
return Books;
79+
}
80+
81+
/// <summary>
82+
///
83+
/// </summary>
84+
/// <param name="book">The book or range of books to process.</param>
85+
/// <returns>A HashSet of books that are part of the range.</returns>
86+
/// <remarks>
87+
/// A "book" may be a single book (GEN), a range of books (e.g. "GEN-LEV"), an entire testament (OT or NT),
88+
/// or marked to remove a book (NT;-REV).
89+
/// </remarks>
90+
private void ProcessBook(string book)
91+
{
92+
switch (book)
93+
{
94+
case "OT":
95+
AddAllBooksForTestament(book);
96+
break;
97+
case "NT":
98+
AddAllBooksForTestament(book);
99+
break;
100+
default:
101+
if (book.Length == 3)
102+
{
103+
Books.Add(book);
104+
}
105+
else if (BookRange.IsMatch(book))
106+
{
107+
AddAllBooksInRange(book);
108+
}
109+
else if (book.StartsWith('-'))
110+
{
111+
BooksToRemove.Add(book[..'-']);
112+
}
113+
else
114+
{
115+
throw new ArgumentException($"Invalid book range: {book}");
116+
}
117+
break;
118+
}
119+
}
120+
121+
private void AddAllBooksInRange(string range)
122+
{
123+
string[] rangeParts = range.Split('-');
124+
// we should expect the first book to come before the second book (e.g. GEN-LEV) not the other way around (e.g. LEV-GEN)
125+
if (Array.IndexOf(Canon.AllBookIds, rangeParts[1]) > Array.IndexOf(Canon.AllBookIds, rangeParts[0]))
126+
{
127+
throw new ArgumentException($"Invalid book range: {range}");
128+
}
129+
130+
string endBook = Canon.AllBookIds[Array.IndexOf(Canon.AllBookIds, rangeParts[1]) + 1];
131+
foreach (
132+
var book in Canon.AllBookIds.SkipWhile(book => book != rangeParts[0]).TakeWhile(book => book != endBook)
133+
)
134+
{
135+
Books.Add(book);
136+
}
137+
}
138+
139+
private void AddAllBooksForTestament(string testment)
140+
{
141+
if (testment == "OT")
142+
{
143+
foreach (var book in (string[])Canon.AllBookIds.Where(Canon.IsBookOT))
144+
{
145+
Books.Add(book.ToUpperInvariant());
146+
}
147+
}
148+
else if (testment == "NT")
149+
{
150+
foreach (var book in (string[])Canon.AllBookIds.Where(Canon.IsBookNT))
151+
{
152+
Books.Add(book.ToUpperInvariant());
153+
}
154+
}
155+
else
156+
{
157+
throw new ArgumentException($"Invalid testament: {testment}");
158+
}
159+
}
160+
}

0 commit comments

Comments
 (0)