feat: urlと画像のパースを追加
This commit is contained in:
parent
ecff061924
commit
5fd15c975b
|
@ -52,6 +52,11 @@ sealed class AstNode {
|
|||
}
|
||||
|
||||
sealed class InlineNode : AstNode(), QuotableNode
|
||||
data class InlineNodes(val nodes: MutableList<InlineNode>) : InlineNode() {
|
||||
override fun print(): String {
|
||||
return nodes.joinToString("") { it.print() }
|
||||
}
|
||||
}
|
||||
data class ItalicNode(val nodes: MutableList<InlineNode>) : InlineNode() {
|
||||
override fun print(): String {
|
||||
return nodes.joinToString("", prefix = "*", postfix = "*") { it.print() }
|
||||
|
@ -70,5 +75,14 @@ sealed class AstNode {
|
|||
return text
|
||||
}
|
||||
}
|
||||
|
||||
data class ImageNode(val urlUrlNode: UrlNode) : InlineNode()
|
||||
|
||||
data class UrlNode(val url: UrlUrlNode, val urlNameNode: UrlNameNode, val urlTitleNode: UrlTitleNode?) :
|
||||
InlineNode()
|
||||
|
||||
data class UrlUrlNode(val url: String) : InlineNode()
|
||||
data class UrlTitleNode(val title: String) : InlineNode()
|
||||
data class UrlNameNode(val name: String) : InlineNode()
|
||||
}
|
||||
|
||||
|
|
|
@ -315,7 +315,7 @@ class Lexer {
|
|||
val nextC = charIterator.peekOrNull() ?: return
|
||||
val nextC2 = iterator.peekOrNull() ?: return
|
||||
if (nextC != nextC2) {
|
||||
tokens.add(Text(urlBuilder.toString()))
|
||||
addText(tokens, urlBuilder.toString())
|
||||
return
|
||||
}
|
||||
urlBuilder.append(nextC2)
|
||||
|
@ -323,7 +323,7 @@ class Lexer {
|
|||
iterator.next()
|
||||
}
|
||||
if (urlBuilder.length == 1) {
|
||||
tokens.add(Text(urlBuilder.toString())) //hだけのときはURLじゃないのでテキストとして追加
|
||||
addText(tokens, urlBuilder.toString()) //hだけのときはURLじゃないのでテキストとして追加
|
||||
} else {
|
||||
while (iterator.hasNext() && (iterator.peekOrNull()
|
||||
?.isWhitespace() != true && iterator.peekOrNull() != ')')
|
||||
|
|
|
@ -14,24 +14,22 @@ class Parser {
|
|||
val nodes = mutableListOf<AstNode>()
|
||||
while (iterator.hasNext()) {
|
||||
val node = when (val next = iterator.next()) {
|
||||
is Asterisk, is InlineCodeBlock, is Strike, is Text -> paragraph(next, iterator)
|
||||
is Break -> null
|
||||
is Asterisk, is InlineCodeBlock, is Strike,
|
||||
is Text, is Whitespace, Exclamation, ParenthesesEnd, ParenthesesStart,
|
||||
SquareBracketStart, SquareBracketEnd, is Url, is UrlTitle -> paragraph(
|
||||
next,
|
||||
iterator
|
||||
)
|
||||
|
||||
is Break -> null //todo ただの改行と段落分けの改行のトークンを分ける
|
||||
is CheckBox -> TODO()
|
||||
is CodeBlock -> TODO()
|
||||
is CodeBlockLanguage -> TODO()
|
||||
Exclamation -> TODO()
|
||||
is Header -> header(next, iterator)
|
||||
is Html -> TODO()
|
||||
is Token.List -> TODO()
|
||||
ParenthesesEnd -> TODO()
|
||||
ParenthesesStart -> TODO()
|
||||
is Quote -> TODO()
|
||||
is Separator -> separator(next, iterator)
|
||||
SquareBracketEnd -> TODO()
|
||||
SquareBracketStart -> TODO()
|
||||
is Url -> TODO()
|
||||
is UrlTitle -> TODO()
|
||||
is Whitespace -> TODO()
|
||||
}
|
||||
if (node != null) {
|
||||
nodes.add(node)
|
||||
|
@ -64,30 +62,96 @@ class Parser {
|
|||
iterator.print()
|
||||
val node = when (token) {
|
||||
is Asterisk -> asterisk(token, iterator)
|
||||
Exclamation -> TODO()
|
||||
Exclamation -> image(Exclamation, iterator)
|
||||
is InlineCodeBlock -> TODO()
|
||||
ParenthesesEnd -> TODO()
|
||||
ParenthesesStart -> TODO()
|
||||
SquareBracketEnd -> TODO()
|
||||
SquareBracketStart -> TODO()
|
||||
ParenthesesEnd -> PlainText(")")
|
||||
ParenthesesStart -> PlainText("(")
|
||||
SquareBracketEnd -> PlainText("]")
|
||||
SquareBracketStart -> url(SquareBracketStart, iterator)
|
||||
is Strike -> TODO()
|
||||
is Text -> plainText(token, iterator)
|
||||
is Url -> TODO()
|
||||
is UrlTitle -> TODO()
|
||||
is Whitespace -> TODO()
|
||||
is UrlTitle -> PlainText("\"${token.title}\"")
|
||||
is Whitespace -> whitespace(token, iterator)
|
||||
else -> TODO()
|
||||
}
|
||||
|
||||
return mutableListOf(node)
|
||||
}
|
||||
|
||||
fun whitespace(token: Whitespace, iterator: PeekableTokenIterator): InlineNode {
|
||||
return PlainText(token.whitespace.toString().repeat(token.count))
|
||||
}
|
||||
|
||||
fun plainText(token: Text, iterator: PeekableTokenIterator): PlainText {
|
||||
return PlainText(token.text)
|
||||
}
|
||||
|
||||
fun image(exclamation: Exclamation, iterator: PeekableTokenIterator): InlineNode {
|
||||
val squareBracketStartToken = iterator.peekOrNull()
|
||||
if (squareBracketStartToken !is SquareBracketStart) {
|
||||
TODO()
|
||||
}
|
||||
val url = url(squareBracketStartToken, iterator)
|
||||
if (url !is UrlNode) {
|
||||
return InlineNodes(mutableListOf(PlainText("!"), url))
|
||||
}
|
||||
return ImageNode(url)
|
||||
}
|
||||
|
||||
fun url(squareBracketStart: SquareBracketStart, iterator: PeekableTokenIterator): InlineNode {
|
||||
val urlNameToken = iterator.peekOrNull()
|
||||
if (urlNameToken !is Text) {
|
||||
return PlainText("[")
|
||||
}
|
||||
val text = iterator.next() as Text //text
|
||||
val urlName = urlName(urlNameToken, iterator)
|
||||
if (iterator.peekOrNull() !is SquareBracketEnd) {
|
||||
return InlineNodes(mutableListOf(PlainText("["), PlainText(text.text)))
|
||||
}
|
||||
iterator.skip() // ]
|
||||
if (iterator.peekOrNull() !is ParenthesesStart) {
|
||||
return InlineNodes(mutableListOf(PlainText("["), PlainText(text.text), PlainText("]")))
|
||||
}
|
||||
iterator.skip() //(
|
||||
if (iterator.peekOrNull() !is Url && iterator.peekOrNull() !is Text) {
|
||||
return InlineNodes(mutableListOf(PlainText("[${text.text}](")))
|
||||
}
|
||||
val textOrUrl = iterator.next()
|
||||
val urlUrlNode = if (textOrUrl is Text) {
|
||||
UrlUrlNode(textOrUrl.text)
|
||||
} else if (textOrUrl is Url) {
|
||||
UrlUrlNode(textOrUrl.url)
|
||||
} else {
|
||||
TODO()
|
||||
}
|
||||
val whitespace = if (iterator.peekOrNull() is Whitespace) {
|
||||
val whitespace = iterator.next() as Whitespace
|
||||
whitespace.whitespace.toString().repeat(whitespace.count)
|
||||
} else {
|
||||
""
|
||||
}
|
||||
val urlTitle = if (iterator.peekOrNull() is UrlTitle) {
|
||||
UrlTitleNode((iterator.next() as UrlTitle).title)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
if (iterator.peekOrNull() !is ParenthesesEnd) {
|
||||
return InlineNodes(mutableListOf(PlainText("[${text.text}](${urlTitle?.title.orEmpty()}$whitespace")))
|
||||
}
|
||||
iterator.skip()
|
||||
return UrlNode(urlUrlNode, urlName, urlTitle)
|
||||
}
|
||||
|
||||
fun urlName(text: Text, iterator: PeekableTokenIterator): UrlNameNode {
|
||||
return UrlNameNode(text.text)
|
||||
}
|
||||
|
||||
fun asterisk(token: Asterisk, iterator: PeekableTokenIterator): InlineNode {
|
||||
var count = token.count
|
||||
var node: InlineNode? = null
|
||||
|
||||
//todo **a*を正しくパースできないので閉じカウンタ的なものを追加し、token.countと閉じカウンタが一致しない場合plaintextに置き換える
|
||||
while ((count > 0)) {
|
||||
if (count == 3) {
|
||||
val italicBold = italic(token, iterator, 3)
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package dev.usbharu.markdown
|
||||
|
||||
import kotlin.collections.List
|
||||
|
||||
class PeekableCharIterator(private val charArray: CharArray) : Iterator<Char> {
|
||||
private var index = 0
|
||||
override fun hasNext(): Boolean = index < charArray.size
|
||||
|
@ -51,7 +49,7 @@ class PeekableTokenIterator(private val tokens: List<Token>) : Iterator<Token> {
|
|||
fun peekOrNull(offset: Int): Token? = tokens.getOrNull(index + offset)
|
||||
|
||||
fun current(): Int = index
|
||||
fun skip(count: Int = 0) {
|
||||
fun skip(count: Int = 1) {
|
||||
index += count
|
||||
}
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ class LexerTest {
|
|||
|
||||
println(actual)
|
||||
|
||||
assertContentEquals(listOf(Text("abcd"), Break(1), Text("efg"), Text("h")), actual)
|
||||
assertContentEquals(listOf(Text("abcd"), Break(1), Text("efgh")), actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -70,7 +70,7 @@ class LexerTest {
|
|||
|
||||
println(actual)
|
||||
|
||||
assertContentEquals(listOf(Text("abcd"), Break(2), Text("efg"), Text("h")), actual)
|
||||
assertContentEquals(listOf(Text("abcd"), Break(2), Text("efgh")), actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -675,6 +675,27 @@ class LexerTest {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun 不正urlとタイトル() {
|
||||
val lexer = Lexer()
|
||||
|
||||
val actual = lexer.lex("[alt](../hoge.html \"example\")")
|
||||
|
||||
println(actual)
|
||||
|
||||
assertContentEquals(
|
||||
listOf(
|
||||
SquareBracketStart,
|
||||
Text("alt"),
|
||||
SquareBracketEnd,
|
||||
ParenthesesStart,
|
||||
Url("https://example.com"),
|
||||
UrlTitle("example"),
|
||||
ParenthesesEnd
|
||||
), actual
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun インラインコードブロック() {
|
||||
val lexer = Lexer()
|
||||
|
|
|
@ -251,4 +251,62 @@ class ParserTest {
|
|||
), actual
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun アスタリスク不正() {
|
||||
val parser = Parser()
|
||||
|
||||
val actual = parser.parse(listOf(Asterisk(2, '*'), Text("a"), Asterisk(1, '*')))
|
||||
|
||||
println(actual)
|
||||
println(actual.print())
|
||||
|
||||
assertEquals(
|
||||
RootNode(
|
||||
BodyNode(
|
||||
listOf(
|
||||
ParagraphNode(listOf(BoldNode(mutableListOf(PlainText("a")))))
|
||||
)
|
||||
)
|
||||
), actual
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun url() {
|
||||
val parser = Parser()
|
||||
|
||||
val actual = parser.parse(
|
||||
listOf(
|
||||
SquareBracketStart,
|
||||
Text("alt"),
|
||||
SquareBracketEnd,
|
||||
ParenthesesStart,
|
||||
Url("https://example.com"),
|
||||
UrlTitle("example"),
|
||||
ParenthesesEnd
|
||||
)
|
||||
)
|
||||
|
||||
println(actual)
|
||||
println(actual.print())
|
||||
|
||||
assertEquals(
|
||||
RootNode(
|
||||
BodyNode(
|
||||
listOf(
|
||||
ParagraphNode(
|
||||
listOf(
|
||||
UrlNode(
|
||||
UrlUrlNode("https://example.com"),
|
||||
UrlNameNode("alt"), UrlTitleNode("example")
|
||||
)
|
||||
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
), actual
|
||||
)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue