mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-14 19:29:36 +01:00
Add BBCode-like markup parser for FormattedMessage.
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
<PackageReference Include="Nett" Version="0.13.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
||||
<PackageReference Include="NGettext" Version="0.6.4" />
|
||||
<PackageReference Include="Pidgin" Version="2.2.0" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.2.0" />
|
||||
<PackageReference Condition="'$(TargetFramework)' == 'net472'" Include="System.Memory" Version="4.5.3" />
|
||||
<PackageReference Include="YamlDotNet" Version="6.1.2" />
|
||||
|
||||
84
Robust.Shared/Utility/FormattedMessage.MarkupParser.cs
Normal file
84
Robust.Shared/Utility/FormattedMessage.MarkupParser.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Pidgin;
|
||||
using Robust.Shared.Maths;
|
||||
using static Pidgin.Parser;
|
||||
using static Pidgin.Parser<char>;
|
||||
|
||||
namespace Robust.Shared.Utility
|
||||
{
|
||||
public partial class FormattedMessage
|
||||
{
|
||||
// wtf I love parser combinators now.
|
||||
private const char TagBegin = '[';
|
||||
private const char TagEnd = ']';
|
||||
|
||||
private static readonly Parser<char, char> ParseEscapeSequence =
|
||||
Char('\\').Then(OneOf(
|
||||
Char('\\'),
|
||||
Char(TagBegin)));
|
||||
|
||||
private static readonly Parser<char, TagText> ParseTagText =
|
||||
ParseEscapeSequence.Or(Token(c => c != TagBegin && c != '\\'))
|
||||
.AtLeastOnceString()
|
||||
.Select(s => new TagText(s));
|
||||
|
||||
private static readonly Parser<char, TagColor> ParseTagColor =
|
||||
String("color")
|
||||
.Then(Char('='))
|
||||
.Then(Token(ValidColorNameContents).AtLeastOnceString()
|
||||
.Select(s =>
|
||||
{
|
||||
if (Color.TryFromName(s, out var color))
|
||||
{
|
||||
return new TagColor(color);
|
||||
}
|
||||
return new TagColor(Color.FromHex(s));
|
||||
}));
|
||||
|
||||
private static readonly Parser<char, TagPop> ParseTagPop =
|
||||
Char('/')
|
||||
.Then(String("color"))
|
||||
.ThenReturn(TagPop.Instance);
|
||||
|
||||
private static readonly Parser<char, Tag> ParseTagContents =
|
||||
ParseTagColor.Cast<Tag>().Or(ParseTagPop.Cast<Tag>());
|
||||
|
||||
private static readonly Parser<char, Tag> ParseEnclosedTag =
|
||||
ParseTagContents.Between(Char(TagBegin), Char(TagEnd));
|
||||
|
||||
private static readonly Parser<char, IEnumerable<Tag>> Parse =
|
||||
ParseTagText.Cast<Tag>().Or(ParseEnclosedTag).Many();
|
||||
|
||||
public void AddMarkup(ReadOnlySpan<char> markup)
|
||||
{
|
||||
_tags.AddRange(Parse.ParseOrThrow(markup));
|
||||
}
|
||||
|
||||
private static bool ValidColorNameContents(char c)
|
||||
{
|
||||
// Match contents of valid color name.
|
||||
if (c == '#')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (c >= 'a' && c <= 'z')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (c >= 'A' && c <= 'Z')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (c >= '0' && c <= '9')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ namespace Robust.Shared.Utility
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class FormattedMessage
|
||||
public sealed partial class FormattedMessage
|
||||
{
|
||||
public TagList Tags => new TagList(_tags);
|
||||
private readonly List<Tag> _tags;
|
||||
@@ -29,6 +29,13 @@ namespace Robust.Shared.Utility
|
||||
_tags = new List<Tag>(capacity);
|
||||
}
|
||||
|
||||
public static FormattedMessage FromMarkup(ReadOnlySpan<char> markup)
|
||||
{
|
||||
var msg = new FormattedMessage();
|
||||
msg.AddMarkup(markup);
|
||||
return msg;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <c>FormattedMessage</c> by copying another one.
|
||||
/// </summary>
|
||||
@@ -109,6 +116,7 @@ namespace Robust.Shared.Utility
|
||||
[Serializable, NetSerializable]
|
||||
public class TagPop : Tag
|
||||
{
|
||||
public static readonly TagPop Instance = new TagPop();
|
||||
}
|
||||
|
||||
public readonly struct TagList : IReadOnlyList<Tag>
|
||||
|
||||
38
Robust.UnitTesting/Shared/Utility/FormattedMessage_Test.cs
Normal file
38
Robust.UnitTesting/Shared/Utility/FormattedMessage_Test.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.Utility
|
||||
{
|
||||
[Parallelizable(ParallelScope.All)]
|
||||
[TestFixture]
|
||||
[TestOf(typeof(FormattedMessage))]
|
||||
public class FormattedMessage_Test
|
||||
{
|
||||
[Test]
|
||||
public static void TestParseMarkup()
|
||||
{
|
||||
var msg = FormattedMessage.FromMarkup("foo[color=#aabbcc]bar[/color]baz");
|
||||
|
||||
Assert.That(msg.Tags.Count, Is.EqualTo(5));
|
||||
Assert.That(((FormattedMessage.TagText) msg.Tags[0]).Text, Is.EqualTo("foo"));
|
||||
Assert.That(((FormattedMessage.TagColor) msg.Tags[1]).Color, Is.EqualTo(Color.FromHex("#aabbcc")));
|
||||
Assert.That(((FormattedMessage.TagText) msg.Tags[2]).Text, Is.EqualTo("bar"));
|
||||
Assert.That(msg.Tags[3], Is.InstanceOf<FormattedMessage.TagPop>());
|
||||
Assert.That(((FormattedMessage.TagText) msg.Tags[4]).Text, Is.EqualTo("baz"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public static void TestParseMarkupColorName()
|
||||
{
|
||||
var msg = FormattedMessage.FromMarkup("foo[color=orange]bar[/color]baz");
|
||||
|
||||
Assert.That(msg.Tags.Count, Is.EqualTo(5));
|
||||
Assert.That(((FormattedMessage.TagText) msg.Tags[0]).Text, Is.EqualTo("foo"));
|
||||
Assert.That(((FormattedMessage.TagColor) msg.Tags[1]).Color, Is.EqualTo(Color.Orange));
|
||||
Assert.That(((FormattedMessage.TagText) msg.Tags[2]).Text, Is.EqualTo("bar"));
|
||||
Assert.That(msg.Tags[3], Is.InstanceOf<FormattedMessage.TagPop>());
|
||||
Assert.That(((FormattedMessage.TagText) msg.Tags[4]).Text, Is.EqualTo("baz"));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user