[parsing] Add strike html tag support, keep italic/bold/strike in html tag form when reserializing
This commit is contained in:
parent
cd84b480a6
commit
2ef78e3f41
3 changed files with 147 additions and 36 deletions
|
@ -35,11 +35,13 @@ public static class MfmSerializer
|
||||||
result.Append(" [search]");
|
result.Append(" [search]");
|
||||||
break;
|
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(SerializeInternal(node.Children));
|
||||||
result.Append("**");
|
result.Append(end);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MfmCenterNode:
|
case MfmCenterNode:
|
||||||
|
@ -80,11 +82,13 @@ public static class MfmSerializer
|
||||||
result.Append($"`{mfmInlineCodeNode.Code}`");
|
result.Append($"`{mfmInlineCodeNode.Code}`");
|
||||||
break;
|
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(SerializeInternal(node.Children));
|
||||||
result.Append('*');
|
result.Append(end);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MfmLinkNode mfmLinkNode:
|
case MfmLinkNode mfmLinkNode:
|
||||||
|
@ -125,11 +129,13 @@ public static class MfmSerializer
|
||||||
result.Append("</small>");
|
result.Append("</small>");
|
||||||
break;
|
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(SerializeInternal(node.Children));
|
||||||
result.Append("~~");
|
result.Append(end);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MfmTextNode mfmTextNode:
|
case MfmTextNode mfmTextNode:
|
||||||
|
|
|
@ -19,18 +19,25 @@ module MfmNodeTypes =
|
||||||
inherit MfmNode()
|
inherit MfmNode()
|
||||||
do base.Children <- c |> List.map (fun x -> x :> MfmNode)
|
do base.Children <- c |> List.map (fun x -> x :> MfmNode)
|
||||||
|
|
||||||
|
type InlineNodeType =
|
||||||
|
| Symbol
|
||||||
|
| HtmlTag
|
||||||
|
|
||||||
type MfmTextNode(v: string) =
|
type MfmTextNode(v: string) =
|
||||||
inherit MfmInlineNode([])
|
inherit MfmInlineNode([])
|
||||||
member val Text = v
|
member val Text = v
|
||||||
|
|
||||||
type MfmItalicNode(c) =
|
type MfmItalicNode(c, t) =
|
||||||
inherit MfmInlineNode(c)
|
inherit MfmInlineNode(c)
|
||||||
|
member val Type: InlineNodeType = t
|
||||||
|
|
||||||
type MfmBoldNode(c) =
|
type MfmBoldNode(c, t) =
|
||||||
inherit MfmInlineNode(c)
|
inherit MfmInlineNode(c)
|
||||||
|
member val Type: InlineNodeType = t
|
||||||
|
|
||||||
type MfmStrikeNode(c) =
|
type MfmStrikeNode(c, t) =
|
||||||
inherit MfmInlineNode(c)
|
inherit MfmInlineNode(c)
|
||||||
|
member val Type: InlineNodeType = t
|
||||||
|
|
||||||
type MfmInlineCodeNode(v: string) =
|
type MfmInlineCodeNode(v: string) =
|
||||||
inherit MfmInlineNode([])
|
inherit MfmInlineNode([])
|
||||||
|
@ -240,7 +247,7 @@ module private MfmParser =
|
||||||
>>. pushLine
|
>>. pushLine
|
||||||
>>. manyTill inlineNode italicPatternAsterisk
|
>>. manyTill inlineNode italicPatternAsterisk
|
||||||
.>> assertLine
|
.>> assertLine
|
||||||
|>> fun c -> MfmItalicNode(aggregateTextInline c) :> MfmNode
|
|>> fun c -> MfmItalicNode(aggregateTextInline c, Symbol) :> MfmNode
|
||||||
|
|
||||||
let italicUnderscoreNode =
|
let italicUnderscoreNode =
|
||||||
previousCharSatisfiesNot isNotWhitespace
|
previousCharSatisfiesNot isNotWhitespace
|
||||||
|
@ -248,11 +255,11 @@ module private MfmParser =
|
||||||
>>. pushLine
|
>>. pushLine
|
||||||
>>. manyTill inlineNode italicPatternUnderscore
|
>>. manyTill inlineNode italicPatternUnderscore
|
||||||
.>> assertLine
|
.>> assertLine
|
||||||
|>> fun c -> MfmItalicNode(aggregateTextInline c) :> MfmNode
|
|>> fun c -> MfmItalicNode(aggregateTextInline c, Symbol) :> MfmNode
|
||||||
|
|
||||||
let italicTagNode =
|
let italicTagNode =
|
||||||
skipString "<i>" >>. manyTill inlineNode (skipString "</i>")
|
skipString "<i>" >>. manyTill inlineNode (skipString "</i>")
|
||||||
|>> fun c -> MfmItalicNode(aggregateTextInline c) :> MfmNode
|
|>> fun c -> MfmItalicNode(aggregateTextInline c, HtmlTag) :> MfmNode
|
||||||
|
|
||||||
let boldAsteriskNode =
|
let boldAsteriskNode =
|
||||||
previousCharSatisfiesNot isNotWhitespace
|
previousCharSatisfiesNot isNotWhitespace
|
||||||
|
@ -260,7 +267,7 @@ module private MfmParser =
|
||||||
>>. pushLine
|
>>. pushLine
|
||||||
>>. manyTill inlineNode (skipString "**")
|
>>. manyTill inlineNode (skipString "**")
|
||||||
.>> assertLine
|
.>> assertLine
|
||||||
|>> fun c -> MfmBoldNode(aggregateTextInline c) :> MfmNode
|
|>> fun c -> MfmBoldNode(aggregateTextInline c, Symbol) :> MfmNode
|
||||||
|
|
||||||
let boldUnderscoreNode =
|
let boldUnderscoreNode =
|
||||||
previousCharSatisfiesNot isNotWhitespace
|
previousCharSatisfiesNot isNotWhitespace
|
||||||
|
@ -268,16 +275,20 @@ module private MfmParser =
|
||||||
>>. pushLine
|
>>. pushLine
|
||||||
>>. manyTill inlineNode (skipString "__")
|
>>. manyTill inlineNode (skipString "__")
|
||||||
.>> assertLine
|
.>> assertLine
|
||||||
|>> fun c -> MfmBoldNode(aggregateTextInline c) :> MfmNode
|
|>> fun c -> MfmBoldNode(aggregateTextInline c, Symbol) :> MfmNode
|
||||||
|
|
||||||
let boldTagNode =
|
let boldTagNode =
|
||||||
skipString "<b>" >>. manyTill inlineNode (skipString "</b>")
|
skipString "<b>" >>. manyTill inlineNode (skipString "</b>")
|
||||||
|>> fun c -> MfmBoldNode(aggregateTextInline c) :> MfmNode
|
|>> fun c -> MfmBoldNode(aggregateTextInline c, HtmlTag) :> MfmNode
|
||||||
|
|
||||||
let strikeNode =
|
let strikeNode =
|
||||||
skipString "~~" >>. pushLine >>. manyTill inlineNode (skipString "~~")
|
skipString "~~" >>. pushLine >>. manyTill inlineNode (skipString "~~")
|
||||||
.>> assertLine
|
.>> 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 =
|
let codeNode =
|
||||||
codePattern >>. pushLine >>. manyCharsTill anyChar codePattern .>> assertLine
|
codePattern >>. pushLine >>. manyCharsTill anyChar codePattern .>> assertLine
|
||||||
|
@ -430,6 +441,14 @@ module private MfmParser =
|
||||||
| Simple
|
| Simple
|
||||||
|
|
||||||
let parseNode (m: ParseMode) =
|
let parseNode (m: ParseMode) =
|
||||||
|
let inlineTagNodes =
|
||||||
|
[ plainNode
|
||||||
|
smallNode
|
||||||
|
italicTagNode
|
||||||
|
boldTagNode
|
||||||
|
strikeTagNode
|
||||||
|
urlNodeBrackets ]
|
||||||
|
|
||||||
let prefixedNode (m: ParseMode) : Parser<MfmNode, UserState> =
|
let prefixedNode (m: ParseMode) : Parser<MfmNode, UserState> =
|
||||||
fun (stream: CharStream<_>) ->
|
fun (stream: CharStream<_>) ->
|
||||||
match (stream.Peek(), m) with
|
match (stream.Peek(), m) with
|
||||||
|
@ -450,7 +469,7 @@ module private MfmParser =
|
||||||
| ':', (Full | Inline | Simple) -> emojiCodeNode
|
| ':', (Full | Inline | Simple) -> emojiCodeNode
|
||||||
| '~', (Full | Inline) when stream.Match "~~" -> strikeNode
|
| '~', (Full | Inline) when stream.Match "~~" -> strikeNode
|
||||||
| '[', (Full | Inline) -> linkNode
|
| '[', (Full | Inline) -> linkNode
|
||||||
| '<', (Full | Inline) -> choice [ plainNode; smallNode; italicTagNode; boldTagNode; urlNodeBrackets ]
|
| '<', (Full | Inline) -> choice inlineTagNodes
|
||||||
| '<', Simple when stream.Match "<plain>" -> plainNode
|
| '<', Simple when stream.Match "<plain>" -> plainNode
|
||||||
| '\\', (Full | Inline) when stream.Match "\\(" -> mathNode
|
| '\\', (Full | Inline) when stream.Match "\\(" -> mathNode
|
||||||
| '$', (Full | Inline) when stream.Match "$[" -> fnNode
|
| '$', (Full | Inline) when stream.Match "$[" -> fnNode
|
||||||
|
|
|
@ -14,30 +14,73 @@ public class MfmTests
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void TestParseBoldItalic()
|
public void TestParseBoldItalic()
|
||||||
{
|
{
|
||||||
List<MfmNode> expected =
|
// @formatter:off
|
||||||
|
List<MfmNode> expected123 =
|
||||||
[
|
[
|
||||||
new MfmItalicNode(ListModule.OfSeq<MfmInlineNode>([
|
new MfmItalicNode(ListModule.OfSeq<MfmInlineNode>([
|
||||||
new MfmTextNode("italic "),
|
new MfmTextNode("italic "),
|
||||||
new MfmBoldNode(ListModule.OfSeq<MfmInlineNode>([new MfmTextNode("bold")])),
|
new MfmBoldNode(ListModule.OfSeq<MfmInlineNode>([new MfmTextNode("bold")]), InlineNodeType.Symbol),
|
||||||
new MfmTextNode(" italic")
|
new MfmTextNode(" italic")
|
||||||
]))
|
]), InlineNodeType.Symbol)
|
||||||
];
|
];
|
||||||
|
|
||||||
var res = Mfm.parse("*italic **bold** italic*").ToList();
|
List<MfmNode> expected4 =
|
||||||
var resAlt = Mfm.parse("_italic **bold** italic_").ToList();
|
[
|
||||||
var resAlt2 = Mfm.parse("_italic __bold__ italic_").ToList();
|
new MfmItalicNode(ListModule.OfSeq<MfmInlineNode>([
|
||||||
var resAlt3 = Mfm.parse("<i>italic <b>bold</b> italic</i>").ToList();
|
new MfmTextNode("italic "),
|
||||||
var resMixed = Mfm.parse("<i>italic **bold** italic</i>").ToList();
|
new MfmBoldNode(ListModule.OfSeq<MfmInlineNode>([new MfmTextNode("bold")]), InlineNodeType.HtmlTag),
|
||||||
var resMixedAlt = Mfm.parse("*italic <b>bold</b> italic*").ToList();
|
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;
|
AssertionOptions.FormattingOptions.MaxDepth = 100;
|
||||||
|
|
||||||
res.Should().Equal(expected, MfmNodeEqual);
|
res.Should().Equal(expected123, MfmNodeEqual);
|
||||||
resAlt.Should().Equal(expected, MfmNodeEqual);
|
res2.Should().Equal(expected123, MfmNodeEqual);
|
||||||
resAlt2.Should().Equal(expected, MfmNodeEqual);
|
res3.Should().Equal(expected123, MfmNodeEqual);
|
||||||
resAlt3.Should().Equal(expected, MfmNodeEqual);
|
res4.Should().Equal(expected4, MfmNodeEqual);
|
||||||
resMixed.Should().Equal(expected, MfmNodeEqual);
|
res5.Should().Equal(expected5, MfmNodeEqual);
|
||||||
resMixedAlt.Should().Equal(expected, 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]
|
[TestMethod]
|
||||||
|
@ -58,7 +101,7 @@ public class MfmTests
|
||||||
expected =
|
expected =
|
||||||
[
|
[
|
||||||
new MfmTextNode("test "),
|
new MfmTextNode("test "),
|
||||||
new MfmItalicNode(ListModule.OfSeq<MfmInlineNode>([new MfmTextNode("test")])),
|
new MfmItalicNode(ListModule.OfSeq<MfmInlineNode>([new MfmTextNode("test")]), InlineNodeType.Symbol),
|
||||||
new MfmTextNode("test")
|
new MfmTextNode("test")
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -66,6 +109,31 @@ public class MfmTests
|
||||||
Mfm.parse("test _test_test").ToList().Should().Equal(expected, MfmNodeEqual);
|
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]
|
[TestMethod]
|
||||||
public void TestParseList()
|
public void TestParseList()
|
||||||
{
|
{
|
||||||
|
@ -511,6 +579,24 @@ public class MfmTests
|
||||||
{
|
{
|
||||||
case MfmTextNode textNode when ((MfmTextNode)b).Text != textNode.Text:
|
case MfmTextNode textNode when ((MfmTextNode)b).Text != textNode.Text:
|
||||||
return false;
|
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:
|
case MfmMentionNode ax:
|
||||||
{
|
{
|
||||||
var bx = (MfmMentionNode)b;
|
var bx = (MfmMentionNode)b;
|
||||||
|
|
Loading…
Add table
Reference in a new issue