mirror of
https://github.com/oliverbooth/X10D
synced 2024-11-23 00:38:47 +00:00
Add IsEmoji method
Emoji validation is performed by using a regex which is generated from Twemoji. Source: https://raw.githubusercontent.com/twitter/twemoji-parser/master/src/lib/regex.js
This commit is contained in:
parent
064011cb68
commit
d405faf6e2
@ -24,6 +24,9 @@
|
|||||||
- Added `IEnumerable<T>.ForEach` (#50)
|
- Added `IEnumerable<T>.ForEach` (#50)
|
||||||
- Added `IEnumerable<T>.DisposeAll`
|
- Added `IEnumerable<T>.DisposeAll`
|
||||||
- Added `IEnumerable<T>.DisposeAllAsync`
|
- Added `IEnumerable<T>.DisposeAllAsync`
|
||||||
|
- Added `char.IsEmoji`
|
||||||
|
- Added `Rune.IsEmoji`
|
||||||
|
- Added `string.IsEmoji`
|
||||||
|
|
||||||
## [3.0.0]
|
## [3.0.0]
|
||||||
|
|
||||||
|
68
X10D.SourceGenerator/EmojiRegexGenerator.cs
Normal file
68
X10D.SourceGenerator/EmojiRegexGenerator.cs
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.Text;
|
||||||
|
|
||||||
|
namespace X10D.SourceGenerator;
|
||||||
|
|
||||||
|
[Generator]
|
||||||
|
internal sealed class EmojiRegexGenerator : ISourceGenerator
|
||||||
|
{
|
||||||
|
// ReSharper disable once IdentifierTypo
|
||||||
|
private const string TwemojiRegexUrl = "https://raw.githubusercontent.com/twitter/twemoji-parser/master/src/lib/regex.js";
|
||||||
|
private static readonly HttpClient HttpClient = new();
|
||||||
|
private string _emojiRegex = string.Empty;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Initialize(GeneratorInitializationContext context)
|
||||||
|
{
|
||||||
|
string response = HttpClient.GetStringAsync(TwemojiRegexUrl).GetAwaiter().GetResult();
|
||||||
|
using var reader = new StringReader(response);
|
||||||
|
|
||||||
|
while (reader.ReadLine() is { } line)
|
||||||
|
{
|
||||||
|
if (!line.StartsWith("export default /"))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Match match = Regex.Match(line, @"export default /(?<regex>.+)/g;");
|
||||||
|
if (!match.Success)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_emojiRegex = $"^{match.Groups["regex"].Value}$";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Execute(GeneratorExecutionContext context)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(_emojiRegex))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
builder.AppendLine("// This file was auto-generated by X10D.SourceGenerator");
|
||||||
|
builder.AppendLine("// Do not edit this file manually");
|
||||||
|
builder.AppendLine();
|
||||||
|
|
||||||
|
builder.AppendLine("using System.Text.RegularExpressions;");
|
||||||
|
builder.AppendLine();
|
||||||
|
builder.AppendLine("namespace X10D.Text;");
|
||||||
|
builder.AppendLine();
|
||||||
|
builder.AppendLine("internal static class EmojiRegex");
|
||||||
|
builder.AppendLine("{");
|
||||||
|
builder.AppendLine(" internal static readonly Regex Value = new Regex(");
|
||||||
|
builder.AppendLine($" @\"{_emojiRegex}\",");
|
||||||
|
// ReSharper disable once StringLiteralTypo
|
||||||
|
builder.AppendLine(" RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.Singleline");
|
||||||
|
builder.AppendLine(" );");
|
||||||
|
builder.AppendLine("}");
|
||||||
|
|
||||||
|
context.AddSource("EmojiRegex.g.cs", SourceText.From(builder.ToString(), Encoding.UTF8));
|
||||||
|
}
|
||||||
|
}
|
18
X10D.SourceGenerator/X10D.SourceGenerator.csproj
Normal file
18
X10D.SourceGenerator/X10D.SourceGenerator.csproj
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<LangVersion>10.0</LangVersion>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" PrivateAssets="all"/>
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
@ -1,4 +1,5 @@
|
|||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using System.Text;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using X10D.Text;
|
using X10D.Text;
|
||||||
|
|
||||||
namespace X10D.Tests.Text;
|
namespace X10D.Tests.Text;
|
||||||
@ -6,6 +7,25 @@ namespace X10D.Tests.Text;
|
|||||||
[TestClass]
|
[TestClass]
|
||||||
public class CharTests
|
public class CharTests
|
||||||
{
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public void IsEmoji_ShouldReturnTrue_GivenBasicEmoji()
|
||||||
|
{
|
||||||
|
Assert.IsTrue('✂'.IsEmoji());
|
||||||
|
Assert.IsTrue('✅'.IsEmoji());
|
||||||
|
Assert.IsTrue('❎'.IsEmoji());
|
||||||
|
Assert.IsTrue('➕'.IsEmoji());
|
||||||
|
Assert.IsTrue('➖'.IsEmoji());
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void IsEmoji_ShouldReturnFalse_GivenNonEmoji()
|
||||||
|
{
|
||||||
|
for (var letter = 'A'; letter <= 'Z'; letter++)
|
||||||
|
{
|
||||||
|
Assert.IsFalse(letter.IsEmoji());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void RepeatShouldBeCorrect()
|
public void RepeatShouldBeCorrect()
|
||||||
{
|
{
|
||||||
|
@ -8,6 +8,25 @@ namespace X10D.Tests.Text;
|
|||||||
[TestClass]
|
[TestClass]
|
||||||
public class RuneTests
|
public class RuneTests
|
||||||
{
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public void IsEmoji_ShouldReturnTrue_GivenBasicEmoji()
|
||||||
|
{
|
||||||
|
Assert.IsTrue(new Rune('✂').IsEmoji());
|
||||||
|
Assert.IsTrue(new Rune('✅').IsEmoji());
|
||||||
|
Assert.IsTrue(new Rune('❎').IsEmoji());
|
||||||
|
Assert.IsTrue(new Rune('➕').IsEmoji());
|
||||||
|
Assert.IsTrue(new Rune('➖').IsEmoji());
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void IsEmoji_ShouldReturnFalse_GivenNonEmoji()
|
||||||
|
{
|
||||||
|
for (var letter = 'A'; letter <= 'Z'; letter++)
|
||||||
|
{
|
||||||
|
Assert.IsFalse(new Rune(letter).IsEmoji());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void RepeatShouldBeCorrect()
|
public void RepeatShouldBeCorrect()
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
#if NET5_0_OR_GREATER
|
#if NET5_0_OR_GREATER
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
#endif
|
#endif
|
||||||
@ -199,6 +199,44 @@ public class StringTests
|
|||||||
Assert.ThrowsException<ArgumentNullException>(() => "Hello World".GetBytes(null!));
|
Assert.ThrowsException<ArgumentNullException>(() => "Hello World".GetBytes(null!));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void IsEmoji_ShouldReturnTrue_GivenBasicEmoji()
|
||||||
|
{
|
||||||
|
Assert.IsTrue("😀".IsEmoji());
|
||||||
|
Assert.IsTrue("🤓".IsEmoji());
|
||||||
|
Assert.IsTrue("🟦".IsEmoji());
|
||||||
|
Assert.IsTrue("🟧".IsEmoji());
|
||||||
|
Assert.IsTrue("🟨".IsEmoji());
|
||||||
|
Assert.IsTrue("🟩".IsEmoji());
|
||||||
|
Assert.IsTrue("🟪".IsEmoji());
|
||||||
|
Assert.IsTrue("🟫".IsEmoji());
|
||||||
|
Assert.IsTrue("📱".IsEmoji());
|
||||||
|
Assert.IsTrue("🎨".IsEmoji());
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void IsEmoji_ShouldReturnTrue_GivenMultiByteEmoji()
|
||||||
|
{
|
||||||
|
string[] regionalIndicatorCodes = Enumerable.Range(0, 26)
|
||||||
|
.Select(i => Encoding.Unicode.GetString(new byte[] {0x3C, 0xD8, (byte)(0xE6 + i), 0xDD}))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
for (var i = 0; i < 26; i++)
|
||||||
|
for (var j = 0; j < 26; j++)
|
||||||
|
{
|
||||||
|
string flag = (regionalIndicatorCodes[i] + regionalIndicatorCodes[j]);
|
||||||
|
Assert.IsTrue(flag.IsEmoji());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void IsEmoji_ShouldReturnFalse_GivenNonEmoji()
|
||||||
|
{
|
||||||
|
Assert.IsFalse("Hello World".IsEmoji());
|
||||||
|
Assert.IsFalse("Hello".IsEmoji());
|
||||||
|
Assert.IsFalse("World".IsEmoji());
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void IsLower_ShouldReturnTrue_GivenLowercaseString()
|
public void IsLower_ShouldReturnTrue_GivenLowercaseString()
|
||||||
{
|
{
|
||||||
|
@ -78,4 +78,10 @@
|
|||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\X10D.SourceGenerator\X10D.SourceGenerator.csproj"
|
||||||
|
OutputItemType="Analyzer"
|
||||||
|
ReferenceOutputAssembly="false"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
@ -8,6 +8,22 @@ namespace X10D.Text;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class CharExtensions
|
public static class CharExtensions
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a value indicating whether this character constitutes an emoji.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The character to check.</param>
|
||||||
|
/// <returns><see langword="true" /> if this character is an emoji; otherwise, <see langword="false" />.</returns>
|
||||||
|
[Pure]
|
||||||
|
#if NETSTANDARD2_1
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
#else
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
#endif
|
||||||
|
public static bool IsEmoji(this char value)
|
||||||
|
{
|
||||||
|
return value.ToString().IsEmoji();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns a string composed of the current character repeated a specified number of times.
|
/// Returns a string composed of the current character repeated a specified number of times.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -10,6 +10,18 @@ namespace X10D.Text;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class RuneExtensions
|
public static class RuneExtensions
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a value indicating whether this rune constitutes an emoji.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The rune to check.</param>
|
||||||
|
/// <returns><see langword="true" /> if this rune is an emoji; otherwise, <see langword="false" />.</returns>
|
||||||
|
[Pure]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
public static bool IsEmoji(this Rune value)
|
||||||
|
{
|
||||||
|
return value.ToString().IsEmoji();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns a string composed of the current rune repeated a specified number of times.
|
/// Returns a string composed of the current rune repeated a specified number of times.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -295,6 +295,31 @@ public static class StringExtensions
|
|||||||
return encoding.GetBytes(value);
|
return encoding.GetBytes(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a value indicating whether this string constitutes an emoji.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The input string.</param>
|
||||||
|
/// <returns><see langword="true" /> if this string is an emoji; otherwise, <see langword="false" />.</returns>
|
||||||
|
[Pure]
|
||||||
|
#if NETSTANDARD2_1
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
#else
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
#endif
|
||||||
|
public static bool IsEmoji(this string value)
|
||||||
|
{
|
||||||
|
#if NET6_0_OR_GREATER
|
||||||
|
ArgumentNullException.ThrowIfNull(value);
|
||||||
|
#else
|
||||||
|
if (value is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(value));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return EmojiRegex.Value.IsMatch(value);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines if all alpha characters in this string are considered lowercase.
|
/// Determines if all alpha characters in this string are considered lowercase.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
Loading…
Reference in New Issue
Block a user