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