Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion MarkdownSpec.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,11 @@ __Непарные_ символы в рамках одного абзаца н

превратится в:

\<h1>Заголовок \<strong>с \<em>разными\</em> символами\</strong>\</h1>
\<h1>Заголовок \<strong>с \<em>разными\</em> символами\</strong>\</h1>

# Image

Конструкция, начинающаяся с ![](), преобразуется в тег \<img src ="" alt ="">.
Внутри квадратных скобок [] указывается альтернативный текст (alt), а внутри круглых скобок () — ссылка на изображение (src).
Альтернативный текст и ссылка могут содержать остальные элементы разметки согласно общим правилам. В альтернативном тексте допускается использование символа [ только при наличии соответствующей закрывающей ].
Аналогично, в URL допускается использование символа ( только при наличии соответствующей закрывающей ).
17 changes: 17 additions & 0 deletions cs/Markdown/Markdown.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="ApprovalTests" Version="7.0.0"/>
<PackageReference Include="FluentAssertions" Version="8.8.0"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1"/>
<PackageReference Include="NUnit" Version="4.4.0"/>
</ItemGroup>

</Project>
18 changes: 18 additions & 0 deletions cs/Markdown/Md.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Markdown.Parsers;
using Markdown.Tokenizer;

namespace Markdown;

public class Md
{
public string Render(string markdownText)
{
var tokenizer = new MarkdownTokenizer(markdownText);
var tokens = tokenizer.Tokenize();
var parser = new MarkdownParser(tokens);
var markdownDocument = parser.ParseTokens();
var htmlText = markdownDocument.ToHtml();

return htmlText;
}
}
18 changes: 18 additions & 0 deletions cs/Markdown/Nodes/Interfaces/InternalMarkdownNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Markdown.Nodes.Interfaces;

public abstract class InternalMarkdownNode : MarkdownNode
{
protected InternalMarkdownNode(string value) : base(value)
{
}

public override void AddChild(MarkdownNode node)
{
Children.Add(node);
}

public override void AddChildren(List<MarkdownNode> nodes)
{
Children.AddRange(nodes);
}
}
8 changes: 8 additions & 0 deletions cs/Markdown/Nodes/Interfaces/LeafMarkdownNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Markdown.Nodes.Interfaces;

public abstract class LeafMarkdownNode : MarkdownNode
{
protected LeafMarkdownNode(string value) : base(value)
{
}
}
22 changes: 22 additions & 0 deletions cs/Markdown/Nodes/Interfaces/MarkdownNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Markdown.Nodes.Interfaces;

public abstract class MarkdownNode
{
public readonly List<MarkdownNode> Children = [];
public readonly string Value;

protected MarkdownNode(string value)
{
Value = value;
}

public virtual void AddChild(MarkdownNode node)
{
}

public virtual void AddChildren(List<MarkdownNode> nodes)
{
}

public abstract string ToHtml();
}
20 changes: 20 additions & 0 deletions cs/Markdown/Nodes/Internal/AltNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Text;
using Markdown.Nodes.Interfaces;

namespace Markdown.Nodes.Internal;

public class AltNode : InternalMarkdownNode
{
public AltNode(string value) : base(value)
{
}

public override string ToHtml()
{
var textBuilder = new StringBuilder();
foreach (var child in Children)
textBuilder.Append(child.ToHtml());

return textBuilder.ToString();
}
}
19 changes: 19 additions & 0 deletions cs/Markdown/Nodes/Internal/BoldNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Text;
using Markdown.Nodes.Interfaces;

namespace Markdown.Nodes.Internal;

public class BoldNode : InternalMarkdownNode
{
public BoldNode(string value) : base(value)
{
}

public override string ToHtml()
{
var textBuilder = new StringBuilder();
foreach (var child in Children) textBuilder.Append(child.ToHtml());

return $"<strong>{textBuilder}</strong>";
}
}
24 changes: 24 additions & 0 deletions cs/Markdown/Nodes/Internal/HeaderNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.Text;
using Markdown.Nodes.Interfaces;

namespace Markdown.Nodes.Internal;

public class HeaderNode : InternalMarkdownNode
{
public HeaderNode(string value) : base(value)
{
}

public override string ToHtml()
{
var textBuilder = new StringBuilder();
var controlCharacters = new StringBuilder();
foreach (var child in Children)
if (child.Value is "\n" or "\r")
controlCharacters.Append(child.ToHtml());
else
textBuilder.Append(child.ToHtml());

return $"<h1>{textBuilder}</h1>{controlCharacters}";
}
}
17 changes: 17 additions & 0 deletions cs/Markdown/Nodes/Internal/ImageNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Markdown.Nodes.Interfaces;

namespace Markdown.Nodes.Internal;

public class ImageNode : InternalMarkdownNode
{
public ImageNode(string value) : base(value)
{
}

public override string ToHtml()
{
var alt = Children[0].ToHtml();
var url = Children[1].ToHtml();
return $"<img src =\"{url}\" alt=\"{alt}\">";
}
}
19 changes: 19 additions & 0 deletions cs/Markdown/Nodes/Internal/ItalicNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Text;
using Markdown.Nodes.Interfaces;

namespace Markdown.Nodes.Internal;

public class ItalicNode : InternalMarkdownNode
{
public ItalicNode(string value) : base(value)
{
}

public override string ToHtml()
{
var textBuilder = new StringBuilder();
foreach (var child in Children) textBuilder.Append(child.ToHtml());

return $"<em>{textBuilder}</em>";
}
}
20 changes: 20 additions & 0 deletions cs/Markdown/Nodes/Internal/MarkdownDocumentNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Text;
using Markdown.Nodes.Interfaces;

namespace Markdown.Nodes.Internal;

public class MarkdownDocumentNode : InternalMarkdownNode
{
public MarkdownDocumentNode(string value) : base(value)
{
}

public override string ToHtml()
{
var textBuilder = new StringBuilder();
foreach (var child in Children)
textBuilder.Append(child.ToHtml());

return textBuilder.ToString();
}
}
15 changes: 15 additions & 0 deletions cs/Markdown/Nodes/Leaf/TextNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Markdown.Nodes.Interfaces;

namespace Markdown.Nodes.Leaf;

public class TextNode : LeafMarkdownNode
{
public TextNode(string value) : base(value)
{
}

public override string ToHtml()
{
return Value;
}
}
15 changes: 15 additions & 0 deletions cs/Markdown/Nodes/Leaf/UrlNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Markdown.Nodes.Interfaces;

namespace Markdown.Nodes.Leaf;

public class UrlNode : LeafMarkdownNode
{
public UrlNode(string value) : base(value)
{
}

public override string ToHtml()
{
return Value;
}
}
28 changes: 28 additions & 0 deletions cs/Markdown/Parsers/EscapeParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using Markdown.Nodes.Interfaces;
using Markdown.Nodes.Leaf;
using Markdown.Parsers.Interfaces;
using Markdown.Tokenizer;

namespace Markdown.Parsers;

public class EscapeParser : IParser
{
private readonly MarkdownParser parser;

public EscapeParser(MarkdownParser parser)
{
this.parser = parser;
}

public ParseStatus TryParse(out MarkdownNode node)
{
node = new TextNode(@"\");
if (parser.CurrentToken.Type != TokenType.Escape)
return ParseStatus.Fail();

parser.MoveNext();
node = new TextNode($"{parser.CurrentToken.Value}");

return ParseStatus.Ok();
}
}
44 changes: 44 additions & 0 deletions cs/Markdown/Parsers/HeaderParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using Markdown.Nodes.Interfaces;
using Markdown.Nodes.Internal;
using Markdown.Nodes.Leaf;
using Markdown.Parsers.Interfaces;
using Markdown.Tokenizer;

namespace Markdown.Parsers;

public class HeaderParser : IParser
{
private readonly MarkdownParser parser;
private readonly HashSet<TokenType> expectedStopSymbols = [TokenType.NewLine, TokenType.Carriage];

public HeaderParser(MarkdownParser parser)
{
this.parser = parser;
}

public ParseStatus TryParse(out MarkdownNode node)
{
if (parser.NextToken != null
&& (parser.CurrentToken.Type != TokenType.Hash
|| (parser.CurrentToken.Type == TokenType.Hash && parser.NextToken.Type != TokenType.Space)))
{
node = new TextNode("#");
return ParseStatus.Fail();
}
parser.MoveNext();

node = new HeaderNode("#");
parser.ParentStack.Push(parser.CurrentParent);
parser.CurrentParent = node;
parser.MoveNext();
parser.ParseTokens(null, expectedStopSymbols);

if (parser.CurrentToken.Type != TokenType.Eof)
node.AddChild(new TextNode($"{parser.CurrentToken.Value}"));

if (!expectedStopSymbols.Contains(parser.CurrentToken.Type))
parser.CurrentParent = parser.ParentStack.Pop();

return ParseStatus.Ok();
}
}
Loading