Skip to content

Commit

Permalink
Merge pull request #276 from andy840119/andy840119/text-tag-utils
Browse files Browse the repository at this point in the history
Implement ruby/romaji tag checking utility.
  • Loading branch information
andy840119 authored Dec 2, 2020
2 parents 14286a2 + 4cfc0a7 commit c12ef25
Show file tree
Hide file tree
Showing 6 changed files with 227 additions and 5 deletions.
142 changes: 142 additions & 0 deletions osu.Game.Rulesets.Karaoke.Tests/Utils/TextTagsUtilsTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// Copyright (c) andy840119 <[email protected]>. Licensed under the GPL Licence.
// See the LICENCE file in the repository root for full licence text.

using Microsoft.EntityFrameworkCore.Internal;
using NUnit.Framework;
using osu.Game.Rulesets.Karaoke.Objects;
using osu.Game.Rulesets.Karaoke.Utils;
using System;
using System.Linq;

namespace osu.Game.Rulesets.Karaoke.Tests.Utils
{
[TestFixture]
public class TextTagsUtilsTest
{
private const string lyric = "Test lyric";

[TestCase(nameof(ValidTextTagWithSorted), TextTagsUtils.Sorting.Asc, new int[] { 0, 1, 1, 2, 2, 3 })]
[TestCase(nameof(ValidTextTagWithSorted), TextTagsUtils.Sorting.Desc, new int[] { 2, 3, 1, 2, 0, 1 })]
[TestCase(nameof(ValidTextTagWithUnsorted), TextTagsUtils.Sorting.Asc, new int[] { 0, 1, 1, 2, 2, 3 })]
[TestCase(nameof(ValidTextTagWithUnsorted), TextTagsUtils.Sorting.Desc, new int[] { 2, 3, 1, 2, 0, 1 })]
public void TestSort(string testCase, TextTagsUtils.Sorting sorting, int[] results)
{
var textTags = getValueByMethodName(testCase);

var sortedTextTags = TextTagsUtils.Sort(textTags, sorting);
for (int i = 0; i < sortedTextTags.Length; i++)
{
// result would be start, end, start, end...
Assert.AreEqual(sortedTextTags[i].StartIndex, results[i * 2]);
Assert.AreEqual(sortedTextTags[i].EndIndex, results[i * 2 + 1]);
}
}

[TestCase(nameof(ValidTextTagWithSorted), TextTagsUtils.Sorting.Asc, new int[] { })]
[TestCase(nameof(ValidTextTagWithUnsorted), TextTagsUtils.Sorting.Asc, new int[] { })]
[TestCase(nameof(InvalidTextTagWithSameStartAndEndIndex), TextTagsUtils.Sorting.Asc, new int[] {0 })]
[TestCase(nameof(InvalidTextTagWithWrongIndex), TextTagsUtils.Sorting.Asc, new int[] { 0 })]
[TestCase(nameof(InvalidTextTagWithNegativeIndex), TextTagsUtils.Sorting.Asc, new int[] { 0 })]
[TestCase(nameof(InvalidTextTagWithEndLargerThenNextStart), TextTagsUtils.Sorting.Asc, new int[] { 1 })]
[TestCase(nameof(InvalidTextTagWithEndLargerThenNextStart), TextTagsUtils.Sorting.Desc, new int[] { 0 })]
[TestCase(nameof(InvalidTextTagWithWrapNextTextTag), TextTagsUtils.Sorting.Asc, new int[] { 1 })]
[TestCase(nameof(InvalidTextTagWithWrapNextTextTag), TextTagsUtils.Sorting.Desc, new int[] { 1 })]
[TestCase(nameof(InvalidTextTagWithSandwichTextTag), TextTagsUtils.Sorting.Asc, new int[] { 1 })]
[TestCase(nameof(InvalidTextTagWithSandwichTextTag), TextTagsUtils.Sorting.Desc, new int[] { 1 })]
public void TestFindInvalid(string testCase, TextTagsUtils.Sorting sorting, int[] errorIndex)
{
var textTags = getValueByMethodName(testCase);

// run all and find invalid indexes.
var invalidTextTag = TextTagsUtils.FindInvalid(textTags, lyric, sorting);
var invalidIndexes = invalidTextTag.Select(v => textTags.IndexOf(v)).ToArray();
Assert.AreEqual(invalidIndexes, errorIndex);
}

private RubyTag[] getValueByMethodName(string methodName)
{
Type thisType = GetType();
var theMethod = thisType.GetMethod(methodName);
if (theMethod == null)
throw new MissingMethodException("Test method is not exist.");

return theMethod.Invoke(this, null) as RubyTag[];
}

#region valid source

public static RubyTag[] ValidTextTagWithSorted()
=> new[]
{
new RubyTag { StartIndex = 0, EndIndex = 1 },
new RubyTag { StartIndex = 1, EndIndex = 2 },
new RubyTag { StartIndex = 2, EndIndex = 3 }
};

public static RubyTag[] ValidTextTagWithUnsorted()
=> new[]
{
new RubyTag { StartIndex = 0, EndIndex = 1 },
new RubyTag { StartIndex = 2, EndIndex = 3 },
new RubyTag { StartIndex = 1, EndIndex = 2 }
};

#endregion

#region invalid source

public static RubyTag[] InvalidTextTagWithWrongIndex()
=> new[]
{
new RubyTag { StartIndex = 1, EndIndex = 0 },
};

public static RubyTag[] InvalidTextTagWithNegativeIndex()
=> new[]
{
new RubyTag { StartIndex = -1, EndIndex = 0 },
};

public static RubyTag[] InvalidTextTagWithSameStartAndEndIndex()
=> new[]
{
new RubyTag { StartIndex = 0, EndIndex = 0 }, // Same number.
};

public static RubyTag[] InvalidTextTagWithStartTimeExceedLyricSize()
=> new[]
{
new RubyTag { StartIndex = 0, EndIndex = lyric.Length + 1 }, // Same number.
};

public static RubyTag[] InvalidTextTagWithEndTimeExceedLyricSize()
=> new[]
{
new RubyTag { StartIndex = lyric.Length + 1, EndIndex = lyric.Length + 2 }, // Same number.
};

public static RubyTag[] InvalidTextTagWithEndLargerThenNextStart()
=> new[]
{
new RubyTag { StartIndex = 0, EndIndex = 2 }, // End is larger than second start.
new RubyTag { StartIndex = 1, EndIndex = 3 }
};

public static RubyTag[] InvalidTextTagWithWrapNextTextTag()
=> new[]
{
new RubyTag { StartIndex = 0, EndIndex = 3 }, // Wrap second text tag.
new RubyTag { StartIndex = 1, EndIndex = 2 }
};

public static RubyTag[] InvalidTextTagWithSandwichTextTag()
=> new[]
{
new RubyTag { StartIndex = 0, EndIndex = 2 },
new RubyTag { StartIndex = 1, EndIndex = 3 },
new RubyTag { StartIndex = 2, EndIndex = 4 }
};

#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

namespace osu.Game.Rulesets.Karaoke.Edit.RubyRomaji.Components
{
public class TagListPreview<T> : Container where T : ITag
public class TagListPreview<T> : Container where T : ITextTag
{
private readonly CornerBackground background;
private readonly PreviewTagTable previewTagTable;
Expand Down Expand Up @@ -148,7 +148,7 @@ private TableColumn[] createHeaders()
private Drawable[] createContent(int index, T tag)
{
// IDK why but it only works with Bindable<ITag>, Bindable<T> doesn't work
var bindableTag = new Bindable<ITag>(tag);
var bindableTag = new Bindable<ITextTag>(tag);

OsuDropdown<int> startPositionDropdown;
OsuDropdown<int> endPositionDropdown;
Expand Down
2 changes: 1 addition & 1 deletion osu.Game.Rulesets.Karaoke/Objects/RomajiTag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace osu.Game.Rulesets.Karaoke.Objects
{
public struct RomajiTag : ITag
public struct RomajiTag : ITextTag
{
/// <summary>
/// If kanji Matched, then apply romaji
Expand Down
2 changes: 1 addition & 1 deletion osu.Game.Rulesets.Karaoke/Objects/RubyTag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace osu.Game.Rulesets.Karaoke.Objects
{
public struct RubyTag : ITag
public struct RubyTag : ITextTag
{
/// <summary>
/// If kanji Matched, then apply ruby
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace osu.Game.Rulesets.Karaoke.Objects.Types
{
public interface ITag
public interface ITextTag : IHasText
{
string Text { get; set; }

Expand Down
80 changes: 80 additions & 0 deletions osu.Game.Rulesets.Karaoke/Utils/TextTagsUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright (c) andy840119 <[email protected]>. Licensed under the GPL Licence.
// See the LICENCE file in the repository root for full licence text.

using osu.Game.Rulesets.Karaoke.Objects.Types;
using System.Collections.Generic;
using System.Linq;
using System;

namespace osu.Game.Rulesets.Karaoke.Utils
{
public static class TextTagsUtils
{
public static T[] Sort<T>(T[] textTags, Sorting sorting = Sorting.Asc) where T : ITextTag
{
switch (sorting)
{
case Sorting.Asc:
return textTags?.OrderBy(x => x.StartIndex).ThenBy(x => x.EndIndex).ToArray();
case Sorting.Desc:
return textTags?.OrderByDescending(x => x.EndIndex).ThenByDescending(x => x.StartIndex).ToArray();
default:
throw new ArgumentOutOfRangeException(nameof(sorting));
}
}

public static T[] FindInvalid<T>(T[] textTags, string lyric, Sorting sorting = Sorting.Asc) where T : ITextTag
{
// check is null or empty
if (textTags == null || textTags.Length == 0)
return new T[] { };

// todo : need to make suure is need to sort in here?
var sortedTextTags = Sort(textTags, sorting);

var invalidList = new List<T>();

// check invalid range
invalidList.AddRange(sortedTextTags.Where(x => x.StartIndex < 0 || x.EndIndex > lyric.Length));

// check end is less or equal to start index
invalidList.AddRange(sortedTextTags.Where(x => x.EndIndex <= x.StartIndex));

// find other is smaller or bigger
foreach (var textTag in sortedTextTags)
{
if (invalidList.Contains(textTag))
continue;

var checkTags = sortedTextTags.Except(new[] { textTag });
switch (sorting)
{
case Sorting.Asc:
// start index within tne target
invalidList.AddRange(checkTags.Where(x => x.StartIndex >= textTag.StartIndex && x.StartIndex < textTag.EndIndex));
break;

case Sorting.Desc:
// end index within tne target
invalidList.AddRange(checkTags.Where(x => x.EndIndex > textTag.StartIndex && x.EndIndex <= textTag.EndIndex));
break;
}
}

return Sort(invalidList.Distinct().ToArray());
}

public enum Sorting
{
/// <summary>
/// Mark next time tag is error if conflict.
/// </summary>
Asc,

/// <summary>
/// Mark previous tag is error if conflict.
/// </summary>
Desc
}
}
}

0 comments on commit c12ef25

Please sign in to comment.