From 1d8a4e1459ac81f39db3a1038ce3f4473cfbcb31 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Mon, 21 Feb 2022 20:50:48 +0800 Subject: [PATCH 1/4] implement base sortable class. --- .../Converters/SortableJsonConvertor.cs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 osu.Game.Rulesets.Karaoke/IO/Serialization/Converters/SortableJsonConvertor.cs diff --git a/osu.Game.Rulesets.Karaoke/IO/Serialization/Converters/SortableJsonConvertor.cs b/osu.Game.Rulesets.Karaoke/IO/Serialization/Converters/SortableJsonConvertor.cs new file mode 100644 index 000000000..1f939e6ae --- /dev/null +++ b/osu.Game.Rulesets.Karaoke/IO/Serialization/Converters/SortableJsonConvertor.cs @@ -0,0 +1,38 @@ +// Copyright (c) andy840119 . Licensed under the GPL Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace osu.Game.Rulesets.Karaoke.IO.Serialization.Converters +{ + public abstract class SortableJsonConvertor : JsonConverter> + { + public sealed override IEnumerable ReadJson(JsonReader reader, Type objectType, IEnumerable existingValue, bool hasExistingValue, JsonSerializer serializer) + { + var obj = JArray.Load(reader); + var timeTags = obj.Select(x => serializer.Deserialize(x.CreateReader())); + return GetSortedValue(timeTags); + } + + public override void WriteJson(JsonWriter writer, IEnumerable value, JsonSerializer serializer) + { + // see: https://stackoverflow.com/questions/3330989/order-of-serialized-fields-using-json-net + var sortedTimeTags = GetSortedValue(value); + + writer.WriteStartArray(); + + foreach (var timeTag in sortedTimeTags) + { + serializer.Serialize(writer, timeTag); + } + + writer.WriteEndArray(); + } + + protected abstract IEnumerable GetSortedValue(IEnumerable objects); + } +} From 2318d0b1861f4b58e7e4a4a13b18cdd8e98496b9 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Tue, 15 Feb 2022 21:28:14 +0800 Subject: [PATCH 2/4] implement time-tags convertor. --- .../Converters/TimeTagsConverterTest.cs | 50 +++++++++++++++++++ .../Converters/TimeTagsConverter.cs | 15 ++++++ 2 files changed, 65 insertions(+) create mode 100644 osu.Game.Rulesets.Karaoke.Tests/IO/Serialization/Converters/TimeTagsConverterTest.cs create mode 100644 osu.Game.Rulesets.Karaoke/IO/Serialization/Converters/TimeTagsConverter.cs diff --git a/osu.Game.Rulesets.Karaoke.Tests/IO/Serialization/Converters/TimeTagsConverterTest.cs b/osu.Game.Rulesets.Karaoke.Tests/IO/Serialization/Converters/TimeTagsConverterTest.cs new file mode 100644 index 000000000..f3a879b25 --- /dev/null +++ b/osu.Game.Rulesets.Karaoke.Tests/IO/Serialization/Converters/TimeTagsConverterTest.cs @@ -0,0 +1,50 @@ +// Copyright (c) andy840119 . Licensed under the GPL Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; +using NUnit.Framework; +using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Karaoke.IO.Serialization.Converters; +using osu.Game.Rulesets.Karaoke.Objects; +using osu.Game.Rulesets.Karaoke.Tests.Asserts; + +namespace osu.Game.Rulesets.Karaoke.Tests.IO.Serialization.Converters +{ + [TestFixture] + public class TimeTagsConverterTest : BaseSingleConverterTest + { + protected override JsonConverter[] CreateExtraConverts() + => new JsonConverter[] + { + new TimeTagConverter(), + }; + + [Test] + public void TestSerialize() + { + var timeTags = new[] + { + new TimeTag(new TextIndex(0, TextIndex.IndexState.End), 1000), + new TimeTag(new TextIndex(0), 0) + }; + + const string expected = "[\"[0,start]:0\",\"[0,end]:1000\"]"; + string actual = JsonConvert.SerializeObject(timeTags, CreateSettings()); + Assert.AreEqual(expected, actual); + } + + [Test] + public void TestDeserialize() + { + const string json = "[\"[0,end]:1000\",\"[0,start]:0\"]"; + + var expected = new[] + { + new TimeTag(new TextIndex(0), 0), + new TimeTag(new TextIndex(0, TextIndex.IndexState.End), 1000), + }; + var actual = JsonConvert.DeserializeObject(json, CreateSettings()); + TimeTagAssert.ArePropertyEqual(expected, actual); + } + } +} diff --git a/osu.Game.Rulesets.Karaoke/IO/Serialization/Converters/TimeTagsConverter.cs b/osu.Game.Rulesets.Karaoke/IO/Serialization/Converters/TimeTagsConverter.cs new file mode 100644 index 000000000..35d42db1b --- /dev/null +++ b/osu.Game.Rulesets.Karaoke/IO/Serialization/Converters/TimeTagsConverter.cs @@ -0,0 +1,15 @@ +// Copyright (c) andy840119 . Licensed under the GPL Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Game.Rulesets.Karaoke.Objects; +using osu.Game.Rulesets.Karaoke.Utils; + +namespace osu.Game.Rulesets.Karaoke.IO.Serialization.Converters +{ + public class TimeTagsConverter : SortableJsonConvertor + { + protected override IEnumerable GetSortedValue(IEnumerable objects) + => TimeTagsUtils.Sort(objects); + } +} From 7125502ea0c90789133f8fe767f3f7793f720c92 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Mon, 21 Feb 2022 21:02:09 +0800 Subject: [PATCH 3/4] implement ruby and romaji tags convertor. --- .../Converters/RomajiTagsConverterTest.cs | 68 +++++++++++++++++++ .../Converters/RubyTagsConverterTest.cs | 68 +++++++++++++++++++ .../Converters/RomajiTagsConverter.cs | 15 ++++ .../Converters/RubyTagsConverter.cs | 15 ++++ 4 files changed, 166 insertions(+) create mode 100644 osu.Game.Rulesets.Karaoke.Tests/IO/Serialization/Converters/RomajiTagsConverterTest.cs create mode 100644 osu.Game.Rulesets.Karaoke.Tests/IO/Serialization/Converters/RubyTagsConverterTest.cs create mode 100644 osu.Game.Rulesets.Karaoke/IO/Serialization/Converters/RomajiTagsConverter.cs create mode 100644 osu.Game.Rulesets.Karaoke/IO/Serialization/Converters/RubyTagsConverter.cs diff --git a/osu.Game.Rulesets.Karaoke.Tests/IO/Serialization/Converters/RomajiTagsConverterTest.cs b/osu.Game.Rulesets.Karaoke.Tests/IO/Serialization/Converters/RomajiTagsConverterTest.cs new file mode 100644 index 000000000..0511d9858 --- /dev/null +++ b/osu.Game.Rulesets.Karaoke.Tests/IO/Serialization/Converters/RomajiTagsConverterTest.cs @@ -0,0 +1,68 @@ +// Copyright (c) andy840119 . Licensed under the GPL Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; +using NUnit.Framework; +using osu.Game.Rulesets.Karaoke.IO.Serialization.Converters; +using osu.Game.Rulesets.Karaoke.Objects; +using osu.Game.Rulesets.Karaoke.Tests.Asserts; + +namespace osu.Game.Rulesets.Karaoke.Tests.IO.Serialization.Converters +{ + public class RomajiTagsConverterTest : BaseSingleConverterTest + { + protected override JsonConverter[] CreateExtraConverts() + => new JsonConverter[] + { + new RomajiTagConverter(), + }; + + [Test] + public void TestSerialize() + { + var timeTags = new[] + { + new RomajiTag + { + StartIndex = 2, + EndIndex = 3, + Text = "ji" + }, + new RomajiTag + { + StartIndex = 1, + EndIndex = 2, + Text = "roma" + }, + }; + + const string expected = "[\"[1,2]:roma\",\"[2,3]:ji\"]"; + string actual = JsonConvert.SerializeObject(timeTags, CreateSettings()); + Assert.AreEqual(expected, actual); + } + + [Test] + public void TestDeserialize() + { + const string json = "[\"[2,3]:ji\",\"[1,2]:roma\"]"; + + var expected = new[] + { + new RomajiTag + { + StartIndex = 1, + EndIndex = 2, + Text = "roma" + }, + new RomajiTag + { + StartIndex = 2, + EndIndex = 3, + Text = "ji" + }, + }; + var actual = JsonConvert.DeserializeObject(json, CreateSettings()); + TextTagAssert.ArePropertyEqual(expected, actual); + } + } +} diff --git a/osu.Game.Rulesets.Karaoke.Tests/IO/Serialization/Converters/RubyTagsConverterTest.cs b/osu.Game.Rulesets.Karaoke.Tests/IO/Serialization/Converters/RubyTagsConverterTest.cs new file mode 100644 index 000000000..324794204 --- /dev/null +++ b/osu.Game.Rulesets.Karaoke.Tests/IO/Serialization/Converters/RubyTagsConverterTest.cs @@ -0,0 +1,68 @@ +// Copyright (c) andy840119 . Licensed under the GPL Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; +using NUnit.Framework; +using osu.Game.Rulesets.Karaoke.IO.Serialization.Converters; +using osu.Game.Rulesets.Karaoke.Objects; +using osu.Game.Rulesets.Karaoke.Tests.Asserts; + +namespace osu.Game.Rulesets.Karaoke.Tests.IO.Serialization.Converters +{ + public class RubyTagsConverterTest : BaseSingleConverterTest + { + protected override JsonConverter[] CreateExtraConverts() + => new JsonConverter[] + { + new RubyTagConverter(), + }; + + [Test] + public void TestSerialize() + { + var timeTags = new[] + { + new RubyTag + { + StartIndex = 2, + EndIndex = 3, + Text = "ビ" + }, + new RubyTag + { + StartIndex = 1, + EndIndex = 2, + Text = "ル" + }, + }; + + const string expected = "[\"[1,2]:ル\",\"[2,3]:ビ\"]"; + string actual = JsonConvert.SerializeObject(timeTags, CreateSettings()); + Assert.AreEqual(expected, actual); + } + + [Test] + public void TestDeserialize() + { + const string json = "[\"[2,3]:ビ\",\"[1,2]:ル\"]"; + + var expected = new[] + { + new RubyTag + { + StartIndex = 1, + EndIndex = 2, + Text = "ル" + }, + new RubyTag + { + StartIndex = 2, + EndIndex = 3, + Text = "ビ" + }, + }; + var actual = JsonConvert.DeserializeObject(json, CreateSettings()); + TextTagAssert.ArePropertyEqual(expected, actual); + } + } +} diff --git a/osu.Game.Rulesets.Karaoke/IO/Serialization/Converters/RomajiTagsConverter.cs b/osu.Game.Rulesets.Karaoke/IO/Serialization/Converters/RomajiTagsConverter.cs new file mode 100644 index 000000000..4b0888dcc --- /dev/null +++ b/osu.Game.Rulesets.Karaoke/IO/Serialization/Converters/RomajiTagsConverter.cs @@ -0,0 +1,15 @@ +// Copyright (c) andy840119 . Licensed under the GPL Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Game.Rulesets.Karaoke.Objects; +using osu.Game.Rulesets.Karaoke.Utils; + +namespace osu.Game.Rulesets.Karaoke.IO.Serialization.Converters +{ + public class RomajiTagsConverter : SortableJsonConvertor + { + protected override IEnumerable GetSortedValue(IEnumerable objects) + => TextTagsUtils.Sort(objects); + } +} diff --git a/osu.Game.Rulesets.Karaoke/IO/Serialization/Converters/RubyTagsConverter.cs b/osu.Game.Rulesets.Karaoke/IO/Serialization/Converters/RubyTagsConverter.cs new file mode 100644 index 000000000..265e4af57 --- /dev/null +++ b/osu.Game.Rulesets.Karaoke/IO/Serialization/Converters/RubyTagsConverter.cs @@ -0,0 +1,15 @@ +// Copyright (c) andy840119 . Licensed under the GPL Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Game.Rulesets.Karaoke.Objects; +using osu.Game.Rulesets.Karaoke.Utils; + +namespace osu.Game.Rulesets.Karaoke.IO.Serialization.Converters +{ + public class RubyTagsConverter : SortableJsonConvertor + { + protected override IEnumerable GetSortedValue(IEnumerable objects) + => TextTagsUtils.Sort(objects); + } +} From 53f8ba176afe9a3a2286335795b06a50d8f2a22b Mon Sep 17 00:00:00 2001 From: andy840119 Date: Mon, 21 Feb 2022 21:08:22 +0800 Subject: [PATCH 4/4] should apply those convertors into encoder/decoder. --- .../Beatmaps/Formats/KaraokeJsonBeatmapDecoder.cs | 3 +++ .../Beatmaps/Formats/KaraokeJsonBeatmapEncoder.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/osu.Game.Rulesets.Karaoke/Beatmaps/Formats/KaraokeJsonBeatmapDecoder.cs b/osu.Game.Rulesets.Karaoke/Beatmaps/Formats/KaraokeJsonBeatmapDecoder.cs index 9c6f7d9be..c5c3db3fd 100644 --- a/osu.Game.Rulesets.Karaoke/Beatmaps/Formats/KaraokeJsonBeatmapDecoder.cs +++ b/osu.Game.Rulesets.Karaoke/Beatmaps/Formats/KaraokeJsonBeatmapDecoder.cs @@ -17,8 +17,11 @@ protected override void ParseStreamInto(LineBufferedReader stream, Beatmap outpu var globalSetting = JsonSerializableExtensions.CreateGlobalSettings(); globalSetting.Converters.Add(new CultureInfoConverter()); globalSetting.Converters.Add(new RomajiTagConverter()); + globalSetting.Converters.Add(new RomajiTagsConverter()); globalSetting.Converters.Add(new RubyTagConverter()); + globalSetting.Converters.Add(new RubyTagsConverter()); globalSetting.Converters.Add(new TimeTagConverter()); + globalSetting.Converters.Add(new TimeTagsConverter()); globalSetting.Converters.Add(new ToneConverter()); // create id if object is by reference. diff --git a/osu.Game.Rulesets.Karaoke/Beatmaps/Formats/KaraokeJsonBeatmapEncoder.cs b/osu.Game.Rulesets.Karaoke/Beatmaps/Formats/KaraokeJsonBeatmapEncoder.cs index d28cde283..27feee1ce 100644 --- a/osu.Game.Rulesets.Karaoke/Beatmaps/Formats/KaraokeJsonBeatmapEncoder.cs +++ b/osu.Game.Rulesets.Karaoke/Beatmaps/Formats/KaraokeJsonBeatmapEncoder.cs @@ -15,8 +15,11 @@ public string Encode(Beatmap output) var globalSetting = JsonSerializableExtensions.CreateGlobalSettings(); globalSetting.Converters.Add(new CultureInfoConverter()); globalSetting.Converters.Add(new RomajiTagConverter()); + globalSetting.Converters.Add(new RomajiTagsConverter()); globalSetting.Converters.Add(new RubyTagConverter()); + globalSetting.Converters.Add(new RubyTagsConverter()); globalSetting.Converters.Add(new TimeTagConverter()); + globalSetting.Converters.Add(new TimeTagsConverter()); globalSetting.Converters.Add(new ToneConverter()); // replace string stream.ReadToEnd().Serialize(output);