[parsing] Add strike html tag support, keep italic/bold/strike in html tag form when reserializing

This commit is contained in:
Laura Hausmann 2024-11-17 16:44:05 +01:00
parent cd84b480a6
commit 2ef78e3f41
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
3 changed files with 147 additions and 36 deletions

View file

@ -35,11 +35,13 @@ public static class MfmSerializer
result.Append(" [search]");
break;
}
case MfmBoldNode:
case MfmBoldNode mfmBoldNode:
{
result.Append("**");
var start = mfmBoldNode.Type.IsSymbol ? "**" : "<b>";
var end = mfmBoldNode.Type.IsSymbol ? "**" : "</b>";
result.Append(start);
result.Append(SerializeInternal(node.Children));
result.Append("**");
result.Append(end);
break;
}
case MfmCenterNode:
@ -80,11 +82,13 @@ public static class MfmSerializer
result.Append($"`{mfmInlineCodeNode.Code}`");
break;
}
case MfmItalicNode:
case MfmItalicNode mfmItalicNode:
{
result.Append('*');
var start = mfmItalicNode.Type.IsSymbol ? "*" : "<i>";
var end = mfmItalicNode.Type.IsSymbol ? "*" : "</i>";
result.Append(start);
result.Append(SerializeInternal(node.Children));
result.Append('*');
result.Append(end);
break;
}
case MfmLinkNode mfmLinkNode:
@ -125,11 +129,13 @@ public static class MfmSerializer
result.Append("</small>");
break;
}
case MfmStrikeNode:
case MfmStrikeNode mfmStrikeNode:
{
result.Append("~~");
var start = mfmStrikeNode.Type.IsSymbol ? "~~" : "<s>";
var end = mfmStrikeNode.Type.IsSymbol ? "~~" : "</s>";
result.Append(start);
result.Append(SerializeInternal(node.Children));
result.Append("~~");
result.Append(end);
break;
}
case MfmTextNode mfmTextNode:

View file

@ -19,18 +19,25 @@ module MfmNodeTypes =
inherit MfmNode()
do base.Children <- c |> List.map (fun x -> x :> MfmNode)
type InlineNodeType =
| Symbol
| HtmlTag
type MfmTextNode(v: string) =
inherit MfmInlineNode([])
member val Text = v
type MfmItalicNode(c) =
type MfmItalicNode(c, t) =
inherit MfmInlineNode(c)
member val Type: InlineNodeType = t
type MfmBoldNode(c) =
type MfmBoldNode(c, t) =
inherit MfmInlineNode(c)
member val Type: InlineNodeType = t
type MfmStrikeNode(c) =
type MfmStrikeNode(c, t) =
inherit MfmInlineNode(c)
member val Type: InlineNodeType = t
type MfmInlineCodeNode(v: string) =
inherit MfmInlineNode([])
@ -240,7 +247,7 @@ module private MfmParser =
>>. pushLine
>>. manyTill inlineNode italicPatternAsterisk
.>> assertLine
|>> fun c -> MfmItalicNode(aggregateTextInline c) :> MfmNode
|>> fun c -> MfmItalicNode(aggregateTextInline c, Symbol) :> MfmNode
let italicUnderscoreNode =
previousCharSatisfiesNot isNotWhitespace
@ -248,11 +255,11 @@ module private MfmParser =
>>. pushLine
>>. manyTill inlineNode italicPatternUnderscore
.>> assertLine
|>> fun c -> MfmItalicNode(aggregateTextInline c) :> MfmNode
|>> fun c -> MfmItalicNode(aggregateTextInline c, Symbol) :> MfmNode
let italicTagNode =
skipString "<i>" >>. manyTill inlineNode (skipString "</i>")
|>> fun c -> MfmItalicNode(aggregateTextInline c) :> MfmNode
|>> fun c -> MfmItalicNode(aggregateTextInline c, HtmlTag) :> MfmNode
let boldAsteriskNode =
previousCharSatisfiesNot isNotWhitespace
@ -260,7 +267,7 @@ module private MfmParser =
>>. pushLine
>>. manyTill inlineNode (skipString "**")
.>> assertLine
|>> fun c -> MfmBoldNode(aggregateTextInline c) :> MfmNode
|>> fun c -> MfmBoldNode(aggregateTextInline c, Symbol) :> MfmNode
let boldUnderscoreNode =
previousCharSatisfiesNot isNotWhitespace
@ -268,16 +275,20 @@ module private MfmParser =
>>. pushLine
>>. manyTill inlineNode (skipString "__")
.>> assertLine
|>> fun c -> MfmBoldNode(aggregateTextInline c) :> MfmNode
|>> fun c -> MfmBoldNode(aggregateTextInline c, Symbol) :> MfmNode
let boldTagNode =
skipString "<b>" >>. manyTill inlineNode (skipString "</b>")
|>> fun c -> MfmBoldNode(aggregateTextInline c) :> MfmNode
|>> fun c -> MfmBoldNode(aggregateTextInline c, HtmlTag) :> MfmNode
let strikeNode =
skipString "~~" >>. pushLine >>. manyTill inlineNode (skipString "~~")
.>> assertLine
|>> fun c -> MfmStrikeNode(aggregateTextInline c) :> MfmNode
|>> fun c -> MfmStrikeNode(aggregateTextInline c, Symbol) :> MfmNode
let strikeTagNode =
skipString "<s>" >>. manyTill inlineNode (skipString "</s>")
|>> fun c -> MfmStrikeNode(aggregateTextInline c, HtmlTag) :> MfmNode
let codeNode =
codePattern >>. pushLine >>. manyCharsTill anyChar codePattern .>> assertLine
@ -430,6 +441,14 @@ module private MfmParser =
| Simple
let parseNode (m: ParseMode) =
let inlineTagNodes =
[ plainNode
smallNode
italicTagNode
boldTagNode
strikeTagNode
urlNodeBrackets ]
let prefixedNode (m: ParseMode) : Parser<MfmNode, UserState> =
fun (stream: CharStream<_>) ->
match (stream.Peek(), m) with
@ -450,7 +469,7 @@ module private MfmParser =
| ':', (Full | Inline | Simple) -> emojiCodeNode
| '~', (Full | Inline) when stream.Match "~~" -> strikeNode
| '[', (Full | Inline) -> linkNode
| '<', (Full | Inline) -> choice [ plainNode; smallNode; italicTagNode; boldTagNode; urlNodeBrackets ]
| '<', (Full | Inline) -> choice inlineTagNodes
| '<', Simple when stream.Match "<plain>" -> plainNode
| '\\', (Full | Inline) when stream.Match "\\(" -> mathNode
| '$', (Full | Inline) when stream.Match "$[" -> fnNode

View file

@ -14,30 +14,73 @@ public class MfmTests
[TestMethod]
public void TestParseBoldItalic()
{
List<MfmNode> expected =
// @formatter:off
List<MfmNode> expected123 =
[
new MfmItalicNode(ListModule.OfSeq<MfmInlineNode>([
new MfmTextNode("italic "),
new MfmBoldNode(ListModule.OfSeq<MfmInlineNode>([new MfmTextNode("bold")])),
new MfmBoldNode(ListModule.OfSeq<MfmInlineNode>([new MfmTextNode("bold")]), InlineNodeType.Symbol),
new MfmTextNode(" italic")
]))
]), InlineNodeType.Symbol)
];
var res = Mfm.parse("*italic **bold** italic*").ToList();
var resAlt = Mfm.parse("_italic **bold** italic_").ToList();
var resAlt2 = Mfm.parse("_italic __bold__ italic_").ToList();
var resAlt3 = Mfm.parse("<i>italic <b>bold</b> italic</i>").ToList();
var resMixed = Mfm.parse("<i>italic **bold** italic</i>").ToList();
var resMixedAlt = Mfm.parse("*italic <b>bold</b> italic*").ToList();
List<MfmNode> expected4 =
[
new MfmItalicNode(ListModule.OfSeq<MfmInlineNode>([
new MfmTextNode("italic "),
new MfmBoldNode(ListModule.OfSeq<MfmInlineNode>([new MfmTextNode("bold")]), InlineNodeType.HtmlTag),
new MfmTextNode(" italic")
]), InlineNodeType.HtmlTag)
];
List<MfmNode> expected5 =
[
new MfmItalicNode(ListModule.OfSeq<MfmInlineNode>([
new MfmTextNode("italic "),
new MfmBoldNode(ListModule.OfSeq<MfmInlineNode>([new MfmTextNode("bold")]), InlineNodeType.Symbol),
new MfmTextNode(" italic")
]), InlineNodeType.HtmlTag)
];
List<MfmNode> expected6 =
[
new MfmItalicNode(ListModule.OfSeq<MfmInlineNode>([
new MfmTextNode("italic "),
new MfmBoldNode(ListModule.OfSeq<MfmInlineNode>([new MfmTextNode("bold")]), InlineNodeType.HtmlTag),
new MfmTextNode(" italic")
]), InlineNodeType.Symbol)
];
// @formatter:on
const string input = "*italic **bold** italic*";
const string input2 = "_italic **bold** italic_";
const string input3 = "_italic __bold__ italic_";
const string input4 = "<i>italic <b>bold</b> italic</i>";
const string input5 = "<i>italic **bold** italic</i>";
const string input6 = "*italic <b>bold</b> italic*";
var res = Mfm.parse(input).ToList();
var res2 = Mfm.parse(input2).ToList();
var res3 = Mfm.parse(input3).ToList();
var res4 = Mfm.parse(input4).ToList();
var res5 = Mfm.parse(input5).ToList();
var res6 = Mfm.parse(input6).ToList();
AssertionOptions.FormattingOptions.MaxDepth = 100;
res.Should().Equal(expected, MfmNodeEqual);
resAlt.Should().Equal(expected, MfmNodeEqual);
resAlt2.Should().Equal(expected, MfmNodeEqual);
resAlt3.Should().Equal(expected, MfmNodeEqual);
resMixed.Should().Equal(expected, MfmNodeEqual);
resMixedAlt.Should().Equal(expected, MfmNodeEqual);
res.Should().Equal(expected123, MfmNodeEqual);
res2.Should().Equal(expected123, MfmNodeEqual);
res3.Should().Equal(expected123, MfmNodeEqual);
res4.Should().Equal(expected4, MfmNodeEqual);
res5.Should().Equal(expected5, MfmNodeEqual);
res6.Should().Equal(expected6, MfmNodeEqual);
MfmSerializer.Serialize(res).Should().BeEquivalentTo(input);
MfmSerializer.Serialize(res2).Should().BeEquivalentTo(input);
MfmSerializer.Serialize(res3).Should().BeEquivalentTo(input);
MfmSerializer.Serialize(res4).Should().BeEquivalentTo(input4);
MfmSerializer.Serialize(res5).Should().BeEquivalentTo(input5);
MfmSerializer.Serialize(res6).Should().BeEquivalentTo(input6);
}
[TestMethod]
@ -58,7 +101,7 @@ public class MfmTests
expected =
[
new MfmTextNode("test "),
new MfmItalicNode(ListModule.OfSeq<MfmInlineNode>([new MfmTextNode("test")])),
new MfmItalicNode(ListModule.OfSeq<MfmInlineNode>([new MfmTextNode("test")]), InlineNodeType.Symbol),
new MfmTextNode("test")
];
@ -66,6 +109,31 @@ public class MfmTests
Mfm.parse("test _test_test").ToList().Should().Equal(expected, MfmNodeEqual);
}
[TestMethod]
public void TestStrike()
{
const string input = "~~test~~";
const string input2 = "<s>test</s>";
List<MfmNode> expected =
[
new MfmStrikeNode(ListModule.OfSeq<MfmInlineNode>([new MfmTextNode("test")]), InlineNodeType.Symbol)
];
List<MfmNode> expected2 =
[
new MfmStrikeNode(ListModule.OfSeq<MfmInlineNode>([new MfmTextNode("test")]), InlineNodeType.HtmlTag)
];
var res = Mfm.parse(input).ToList();
var res2 = Mfm.parse(input2).ToList();
res.Should().Equal(expected, MfmNodeEqual);
res2.Should().Equal(expected2, MfmNodeEqual);
MfmSerializer.Serialize(res).Should().BeEquivalentTo(input);
MfmSerializer.Serialize(res2).Should().BeEquivalentTo(input2);
}
[TestMethod]
public void TestParseList()
{
@ -511,6 +579,24 @@ public class MfmTests
{
case MfmTextNode textNode when ((MfmTextNode)b).Text != textNode.Text:
return false;
case MfmItalicNode ax:
{
var bx = (MfmItalicNode)b;
if (!bx.Type.Equals(ax.Type)) return false;
break;
}
case MfmBoldNode ax:
{
var bx = (MfmBoldNode)b;
if (!bx.Type.Equals(ax.Type)) return false;
break;
}
case MfmStrikeNode ax:
{
var bx = (MfmStrikeNode)b;
if (!bx.Type.Equals(ax.Type)) return false;
break;
}
case MfmMentionNode ax:
{
var bx = (MfmMentionNode)b;