Skip to content

Commit

Permalink
feat: russian language (#18)
Browse files Browse the repository at this point in the history
* ci: update dotnet version

* fix: failing unit tests

* feat: add russian language support (#17)
  • Loading branch information
AnsgarLichter authored Mar 10, 2024
1 parent f2f766c commit e5101a4
Show file tree
Hide file tree
Showing 8 changed files with 320 additions and 14 deletions.
45 changes: 45 additions & 0 deletions ClippingsExamples/MyClippingsRussian.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
Долгая прогулка (Стивен Кинг)
– Ваша заметка в месте 5685 | Добавлено: среда, 28 февраля 2024 г. в 10:07:53

Говорит Бейкер, один из идущих
==========
Долгая прогулка (Стивен Кинг)
– Ваш выделенный отрывок в месте 5683–5685 | Добавлено: среда, 28 февраля 2024 г. в 10:07:53

Вспоминаю, как читал «Женщину в белом» Уилки Коллинза…
==========
Врата Абаддона (Кори Джеймс)
– Ваш выделенный отрывок в месте 442–442 | Добавлено: среда, 28 февраля 2024 г. в 11:30:54

Капитан
==========
Джонатан Стрендж и мистер Норрелл (Сюзанна Кларк)
– Ваш выделенный отрывок в месте 142–142 | Добавлено: среда, 28 февраля 2024 г. в 15:21:17

Вальтера
==========
Джонатан Стрендж и мистер Норрелл (Сюзанна Кларк)
– Ваша заметка в месте 142 | Добавлено: среда, 28 февраля 2024 г. в 15:21:46

ага
==========
Врата Абаддона (Джеймс Кори)
– Ваша заметка на странице 868 | Место 8411 | Добавлено: воскресенье, 3 марта 2024 г. в 18:08:16

Проверка
==========
Врата Абаддона (Джеймс Кори)
– Ваш выделенный отрывок на странице 867 | Место 8408–8411 | Добавлено: воскресенье, 3 марта 2024 г. в 18:08:16

Дверь открылась, и он рывком сдвинул джойстик вперед. Мех провел его сквозь проем. Закрыв за собой, Бык, не медля и не раздумывая, свернул по коридору к внутреннему лифту, к долгому переходу на второй уровень, к сектору М.
==========
К востоку от Эдема (Джон Стейнбек)
– Ваша заметка на странице 6 | Место 55 | Добавлено: воскресенье, 3 марта 2024 г. в 18:48:36

Проверка
==========
К востоку от Эдема (Джон Стейнбек)
– Ваш выделенный отрывок на странице 6 | Место 53–55 | Добавлено: воскресенье, 3 марта 2024 г. в 18:48:36

Салинас-Вэлли расположен в Северной Калифорнии и представляет собой длинную узкую полоску равнины между двумя цепями гор, посреди которой бежит, извиваясь
==========
4 changes: 3 additions & 1 deletion ExportKindleClippingsToNotion/Parser/ClippingsLanguage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ public class ClippingsLanguage()
{
private const string EnglishClipping = "Your Highlight on page";
private const string GermanClipping = "Ihre Markierung bei Position";
private const string RussianClipping = "Ваша заметка в месте";
private const string SpanishClipping = "La subrayado en la página";

private readonly Dictionary<SupportedLanguages, string> _languageIdentifiers =
new()
{
{ SupportedLanguages.English, EnglishClipping },
{ SupportedLanguages.German, GermanClipping }
{ SupportedLanguages.German, GermanClipping },
{ SupportedLanguages.Russian, RussianClipping }
};

public SupportedLanguages Determine(string clipping)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ public record ClippingsLanguageConfiguration
public required Regex FinishPosition { get; init; }
public required Regex ClippingsLimitReached { get; init; }

public required CultureInfo CultureInfo { get; init; }
public required CultureInfo CultureInfo { get; set; }
}
28 changes: 17 additions & 11 deletions ExportKindleClippingsToNotion/Parser/ClippingsParser.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
using ExportKindleClippingsToNotion.Model;
using ExportKindleClippingsToNotion.Model.Dto;
using ExportKindleClippingsToNotion.Model.Dto;

namespace ExportKindleClippingsToNotion.Parser;

public abstract class ClippingsParser(ClippingsLanguageConfiguration languageConfiguration) : IClippingsParser
public abstract class ClippingsParser : IClippingsParser
{
protected readonly ClippingsLanguageConfiguration LanguageConfiguration;

protected ClippingsParser(ClippingsLanguageConfiguration languageConfiguration)
{
LanguageConfiguration = languageConfiguration;
}

public ClippingDto? Parse(string clipping)
{
var lines = clipping.Split("\n");
Expand All @@ -15,17 +21,17 @@ public abstract class ClippingsParser(ClippingsLanguageConfiguration languageCon
}

var lineTitleAndAuthor = lines[0];
var title = languageConfiguration.Title.Match(lineTitleAndAuthor).Value;
var author = languageConfiguration.Author.Match(lineTitleAndAuthor).Value;
var title = LanguageConfiguration.Title.Match(lineTitleAndAuthor).Value;
var author = LanguageConfiguration.Author.Match(lineTitleAndAuthor).Value;

var linePagePositionDate = lines[1];
var page = languageConfiguration.Page.Match(linePagePositionDate).Value;
var startPosition = languageConfiguration.StartPosition.Match(linePagePositionDate).Value;
var finishPosition = languageConfiguration.FinishPosition.Match(linePagePositionDate).Value;
var date = languageConfiguration.Date.Match(linePagePositionDate).Value;
var dateTime = date.Trim().Equals("") ? DateTime.Now : DateTime.Parse(date, languageConfiguration.CultureInfo);
var page = LanguageConfiguration.Page.Match(linePagePositionDate).Value;
var startPosition = LanguageConfiguration.StartPosition.Match(linePagePositionDate).Value;
var finishPosition = LanguageConfiguration.FinishPosition.Match(linePagePositionDate).Value;
var date = LanguageConfiguration.Date.Match(linePagePositionDate).Value;
var dateTime = date.Trim().Equals("") ? DateTime.Now : DateTime.Parse(date, LanguageConfiguration.CultureInfo);
var text = lines[3];
if (languageConfiguration.ClippingsLimitReached.IsMatch(text))
if (LanguageConfiguration.ClippingsLimitReached.IsMatch(text))
{
Console.WriteLine("Skipping clipping because limit has been reached");
return null;
Expand Down
24 changes: 24 additions & 0 deletions ExportKindleClippingsToNotion/Parser/ClippingsParserRussian.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.Globalization;
using System.Text.RegularExpressions;

namespace ExportKindleClippingsToNotion.Parser;

public class ClippingsParserRussian : ClippingsParser
{
public ClippingsParserRussian() : base(new ClippingsLanguageConfiguration
{
Author = new Regex(@"(?<=\()(?!.+?\()(.+?)(?=\))"),
Title = new Regex(@"^[^(]+?(?=\s\()"),
Date = new Regex(@"(?<=Добавлено: )(\p{L}+, \d{1,2} \p{L}+ \d{4}) г. в \d{1,2}:\d{2}:\d{2}"),
Page = new Regex("(?<=странице )[0-9]*"),
StartPosition = new Regex(@"(?<=месте\s|Место\s)\d+"),
FinishPosition = new Regex("(?<=–)[0-9]+"),
ClippingsLimitReached = new Regex("<.+?>"),
CultureInfo = new CultureInfo("ru-RU")
})
{
var dateTimeFormat = LanguageConfiguration.CultureInfo.DateTimeFormat;
dateTimeFormat.LongDatePattern = "dddd, d MMMM yyyy 'г. в'";
dateTimeFormat.LongTimePattern = "HH:mm:ss";
}
}
4 changes: 3 additions & 1 deletion ExportKindleClippingsToNotion/Parser/SupportedLanguages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@ public enum SupportedLanguages
[StringValue("ENG")]
English,
[StringValue("DEU")]
German
German,
[StringValue("RUS")]
Russian
}
17 changes: 17 additions & 0 deletions UnitTests/Parser/ClippingsLanguageTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ Robert C. Martin (Clean Code A Handbook of Agile Software Craftsmanship-Prentice
==========
""";

public const string RussianClipping = """
Долгая прогулка (Стивен Кинг)
– Ваша заметка в месте 5685 | Добавлено: среда, 28 февраля 2024 г. в 10:07:53
Говорит Бейкер, один из идущих
==========
""";

public const string SpanishClipping = """
How To Win Friends and Influence People (Carnegie, Dale)
- La subrayado en la página 79 | posición 1293-1295 | Añadido el martes, 30 de agosto de 2022 19:40:15
Expand Down Expand Up @@ -56,6 +64,15 @@ public void ReturnsGerman()

Assert.Equal(SupportedLanguages.German, determinedLanguage);
}

[Fact]
public void ReturnsRussian()
{
var clippingsLanguage = new ClippingsLanguage();
var determinedLanguage = clippingsLanguage.Determine(RussianClipping);

Assert.Equal(SupportedLanguages.Russian, determinedLanguage);
}

[Theory]
[ClassData(typeof(UnsupportedLanguages))]
Expand Down
210 changes: 210 additions & 0 deletions UnitTests/Parser/ClippingsParserRussianTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
using System.Collections;
using System.Globalization;
using ExportKindleClippingsToNotion.Parser;
using JetBrains.Annotations;

namespace UnitTests.Parser;

[TestSubject(typeof(ClippingsParserRussian))]
public class ClippingsParserRussianTest
{
[Fact]
public void ReturnsNullForAnInvalidClipping()
{
var parser = new ClippingsParserRussian();
const string clipping = "line1\nline2\nline3";

Assert.Null(parser.Parse(clipping));
}

[Theory]
[ClassData(typeof(ValidRussianClippingTestData))]
public void ReturnsAValidClipping(string clipping, string expectedAuthor, string expectedTitle,
int expectedStartPosition, int expectedFinishPosition, int expectedPage, DateTime expectedHighlightDate,
string expectedText)
{
var parser = new ClippingsParserRussian();
var result = parser.Parse(clipping);

Assert.NotNull(result);
Assert.Equal(expectedAuthor, result.Author);
Assert.Equal(expectedTitle, result.Title);
Assert.Equal(expectedStartPosition, result.StartPosition);
Assert.Equal(expectedFinishPosition, result.FinishPosition);
Assert.Equal(expectedPage, result.Page);
Assert.Equal(expectedHighlightDate, result.HighlightDate);
Assert.Equal(expectedText, result.Text);
}

[Theory]
[ClassData(typeof(InvalidRussianClippingTestData))]
public void ReturnsBestMatchForInvalidClipping(string clipping, string expectedAuthor, string expectedTitle,
int expectedStartPosition, int expectedFinishPosition, int expectedPage, DateTime expectedHighlightDate,
string expectedText)
{
var parser = new ClippingsParserRussian();
var result = parser.Parse(clipping);

Assert.NotNull(result);
Assert.Equal(expectedAuthor, result.Author);
Assert.Equal(expectedTitle, result.Title);
Assert.Equal(expectedStartPosition, result.StartPosition);
Assert.Equal(expectedFinishPosition, result.FinishPosition);
Assert.Equal(expectedPage, result.Page);
Assert.Equal(expectedHighlightDate, result.HighlightDate);
Assert.Equal(expectedText, result.Text);
}

[Fact]
public void ReturnsNullIfLimitHasBeenReached()
{
const string clipping =
"Врата Абаддона (Кори Джеймс)\n– Ваш выделенный отрывок в месте 442–442 | Добавлено: среда, 28 февраля 2024 г. в 11:30:54\n\n <Вы достигли максимального количества элементов буфера обмена для этого содержимого> ";
var parser = new ClippingsParserEnglish();

Assert.Null(parser.Parse(clipping));
}
}

public class ValidRussianClippingTestData : IEnumerable<object[]>
{
public IEnumerator<object[]> GetEnumerator()
{
yield return new object[]
{
"Долгая прогулка (Стивен Кинг)\n- Ваша заметка в месте 5685 | Добавлено: среда, 28 февраля 2024 г. в 10:07:53\n\nГоворит Бейкер, один из идущих",
"Стивен Кинг",
"Долгая прогулка",
5685,
0,
0,
DateTime.Parse("2024-02-28T10:07:53"),
"Говорит Бейкер, один из идущих"
};

yield return new object[]
{
"Долгая прогулка (Стивен Кинг)\n– Ваш выделенный отрывок в месте 5683–5685 | Добавлено: среда, 28 февраля 2024 г. в 10:07:53\n\nВспоминаю, как читал «Женщину в белом» Уилки Коллинза…",
"Стивен Кинг",
"Долгая прогулка",
5683,
5685,
0,
DateTime.Parse("2024-02-28T10:07:53"),
"Вспоминаю, как читал «Женщину в белом» Уилки Коллинза…"
};

yield return new object[]
{
"Врата Абаддона (Кори Джеймс)\n– Ваш выделенный отрывок в месте 442–442 | Добавлено: среда, 28 февраля 2024 г. в 11:30:54\n\nКапитан",
"Кори Джеймс",
"Врата Абаддона",
442,
442,
0,
DateTime.Parse("2024-02-28T11:30:54"),
"Капитан"
};

yield return new object[]
{
"Врата Абаддона (Кори Джеймс)\n– Ваш выделенный отрывок в месте 442–442 | Добавлено: среда, 28 февраля 2024 г. в 11:30:54\n\nКапитан",
"Кори Джеймс",
"Врата Абаддона",
442,
442,
0,
DateTime.Parse("2024-02-28T11:30:54"),
"Капитан"
};

yield return new object[]
{
"Джонатан Стрендж и мистер Норрелл (Сюзанна Кларк)\n– Ваш выделенный отрывок в месте 142–142 | Добавлено: среда, 28 февраля 2024 г. в 15:21:17\n\nВальтера",
"Сюзанна Кларк",
"Джонатан Стрендж и мистер Норрелл",
142,
142,
0,
DateTime.Parse("2024-02-28T15:21:17"),
"Вальтера"
};
yield return new object[]
{
"Джонатан Стрендж и мистер Норрелл (Сюзанна Кларк)\n– Ваша заметка в месте 142 | Добавлено: среда, 28 февраля 2024 г. в 15:21:46\n\nага",
"Сюзанна Кларк",
"Джонатан Стрендж и мистер Норрелл",
142,
0,
0,
DateTime.Parse("2024-02-28T15:21:46"),
"ага"
};
yield return new object[]
{
"Врата Абаддона (Джеймс Кори)\n– Ваша заметка на странице 868 | Место 8411 | Добавлено: воскресенье, 3 марта 2024 г. в 18:08:16\n\nПроверка",
"Джеймс Кори",
"Врата Абаддона",
8411,
0,
868,
DateTime.Parse("2024-03-03T18:08:16"),
"Проверка"
};
yield return new object[]
{
"Врата Абаддона (Джеймс Кори)\n– Ваш выделенный отрывок на странице 867 | Место 8408–8411 | Добавлено: воскресенье, 3 марта 2024 г. в 18:08:16\n\nДверь открылась, и он рывком сдвинул джойстик вперед. Мех провел его сквозь проем. Закрыв за собой, Бык, не медля и не раздумывая, свернул по коридору к внутреннему лифту, к долгому переходу на второй уровень, к сектору М.",
"Джеймс Кори",
"Врата Абаддона",
8408,
8411,
867,
DateTime.Parse("2024-03-03T18:08:16"),
"Дверь открылась, и он рывком сдвинул джойстик вперед. Мех провел его сквозь проем. Закрыв за собой, Бык, не медля и не раздумывая, свернул по коридору к внутреннему лифту, к долгому переходу на второй уровень, к сектору М."
};
yield return new object[]
{
"К востоку от Эдема (Джон Стейнбек)\n– Ваша заметка на странице 6 | Место 55 | Добавлено: воскресенье, 3 марта 2024 г. в 18:48:36\n\nПроверка",
"Джон Стейнбек",
"К востоку от Эдема",
55,
0,
6,
DateTime.Parse("2024-03-03T18:48:36"),
"Проверка"
};
yield return new object[]
{
"К востоку от Эдема (Джон Стейнбек)\n– Ваш выделенный отрывок на странице 6 | Место 53–55 | Добавлено: воскресенье, 3 марта 2024 г. в 18:48:36\n\nСалинас-Вэлли расположен в Северной Калифорнии и представляет собой длинную узкую полоску равнины между двумя цепями гор, посреди которой бежит, извиваясь",
"Джон Стейнбек",
"К востоку от Эдема",
53,
55,
6,
DateTime.Parse("2024-03-03T18:48:36"),
"Салинас-Вэлли расположен в Северной Калифорнии и представляет собой длинную узкую полоску равнины между двумя цепями гор, посреди которой бежит, извиваясь"
};
}

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class InvalidRussianClippingTestData : IEnumerable<object[]>
{
public IEnumerator<object[]> GetEnumerator()
{
yield return new object[]
{
"Джеймс Кори (Врата Абаддона(2008))\n– Ваша заметка на странице 868 | Место 8411 | Добавлено: воскресенье, 3 марта 2024 г. в 18:08:16\n\nПроверка",
"2008",
"Джеймс Кори",
8411,
0,
868,
DateTime.Parse("2024-03-03T18:08:16"),
"Проверка"
};
}

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

0 comments on commit e5101a4

Please sign in to comment.