generated from rnentjes/kotlin-server-web-empty
214 lines
5.9 KiB
Kotlin
214 lines
5.9 KiB
Kotlin
package nl.astraeus.markdown.parser
|
|
|
|
import nl.astraeus.markdown.parser.MarkdownPart.ParagraphPart.Bold
|
|
import nl.astraeus.markdown.parser.MarkdownPart.ParagraphPart.BoldItalic
|
|
import nl.astraeus.markdown.parser.MarkdownPart.ParagraphPart.Image
|
|
import nl.astraeus.markdown.parser.MarkdownPart.ParagraphPart.InlineCode
|
|
import nl.astraeus.markdown.parser.MarkdownPart.ParagraphPart.Italic
|
|
import nl.astraeus.markdown.parser.MarkdownPart.ParagraphPart.LineBreak
|
|
import nl.astraeus.markdown.parser.MarkdownPart.ParagraphPart.Link
|
|
import nl.astraeus.markdown.parser.MarkdownPart.ParagraphPart.StrikeThrough
|
|
import nl.astraeus.markdown.parser.MarkdownPart.ParagraphPart.Text
|
|
|
|
private enum class ParType {
|
|
TEXT,
|
|
CODE,
|
|
LINK_LABEL,
|
|
LINK_URL,
|
|
LINK_TITLE,
|
|
LINK_END,
|
|
BOLD,
|
|
ITALIC,
|
|
BOLD_ITALIC,
|
|
STRIKETHROUGH,
|
|
INLINE_CODE,
|
|
IMAGE_ALT,
|
|
IMAGE_SRC,
|
|
LINK_IMAGE_ALT,
|
|
LINK_IMAGE_SRC,
|
|
LINK_IMAGE_LINK,
|
|
AUTOLINK,
|
|
}
|
|
|
|
private typealias ParagraphData = MutableMap<ParType, String>
|
|
|
|
private data class ParState(
|
|
val fromType: ParType,
|
|
val text: String,
|
|
val toType: ParType,
|
|
val out: (ParagraphData) -> MarkdownPart.ParagraphPart? = { _ -> null }
|
|
)
|
|
|
|
private val states = listOf(
|
|
// Inline Code
|
|
ParState(ParType.TEXT, "`", ParType.INLINE_CODE) { data ->
|
|
Text(data[ParType.TEXT]!!)
|
|
},
|
|
ParState(ParType.INLINE_CODE, "`", ParType.TEXT) { data ->
|
|
InlineCode(data[ParType.INLINE_CODE]!!)
|
|
},
|
|
|
|
// Image with link
|
|
ParState(ParType.TEXT, "[![", ParType.LINK_IMAGE_ALT) { data ->
|
|
Text(data[ParType.TEXT]!!)
|
|
},
|
|
ParState(ParType.LINK_IMAGE_ALT, "](", ParType.LINK_IMAGE_SRC),
|
|
ParState(ParType.LINK_IMAGE_SRC, ")](", ParType.LINK_IMAGE_LINK),
|
|
ParState(ParType.LINK_IMAGE_LINK, ")", ParType.TEXT) { data ->
|
|
Image(
|
|
data[ParType.LINK_IMAGE_ALT]!!,
|
|
data[ParType.LINK_IMAGE_SRC]!!,
|
|
data[ParType.LINK_IMAGE_LINK],
|
|
)
|
|
},
|
|
|
|
// Image without link
|
|
ParState(ParType.TEXT, "![", ParType.IMAGE_ALT) { data ->
|
|
Text(data[ParType.TEXT]!!)
|
|
},
|
|
ParState(ParType.IMAGE_ALT, "](", ParType.IMAGE_SRC),
|
|
ParState(ParType.IMAGE_SRC, ")", ParType.TEXT) { data ->
|
|
Image(
|
|
data[ParType.IMAGE_ALT]!!,
|
|
data[ParType.IMAGE_SRC]!!,
|
|
)
|
|
},
|
|
|
|
// Links
|
|
ParState(ParType.TEXT, "[", ParType.LINK_LABEL) { data ->
|
|
Text(data[ParType.TEXT]!!)
|
|
},
|
|
ParState(ParType.LINK_LABEL, "](", ParType.LINK_URL),
|
|
ParState(ParType.LINK_LABEL, "]", ParType.LINK_URL) { data ->
|
|
Text(data[ParType.LINK_LABEL]!!)
|
|
},
|
|
ParState(ParType.LINK_URL, ")", ParType.TEXT) { data ->
|
|
Link(data[ParType.LINK_URL]!!, data[ParType.LINK_LABEL])
|
|
},
|
|
|
|
ParState(ParType.LINK_URL, "\"", ParType.LINK_TITLE),
|
|
ParState(ParType.LINK_TITLE, "\"", ParType.LINK_END),
|
|
ParState(ParType.LINK_END, ")", ParType.TEXT) { data ->
|
|
Link(
|
|
data[ParType.LINK_URL]!!,
|
|
data[ParType.LINK_LABEL],
|
|
data[ParType.LINK_TITLE],
|
|
)
|
|
},
|
|
|
|
ParState(ParType.TEXT, "***", ParType.BOLD_ITALIC) { data ->
|
|
Text(data[ParType.TEXT]!!)
|
|
},
|
|
ParState(ParType.BOLD_ITALIC, "***", ParType.TEXT) { data ->
|
|
BoldItalic(data[ParType.BOLD_ITALIC]!!)
|
|
},
|
|
|
|
ParState(ParType.TEXT, "~~", ParType.STRIKETHROUGH) { data ->
|
|
Text(data[ParType.TEXT]!!)
|
|
},
|
|
ParState(ParType.STRIKETHROUGH, "~~", ParType.TEXT) { data ->
|
|
StrikeThrough(data[ParType.STRIKETHROUGH]!!)
|
|
},
|
|
|
|
ParState(ParType.TEXT, "**", ParType.BOLD) { data ->
|
|
Text(data[ParType.TEXT]!!)
|
|
},
|
|
ParState(ParType.BOLD, "**", ParType.TEXT) { data ->
|
|
Bold(data[ParType.BOLD]!!)
|
|
},
|
|
|
|
ParState(ParType.TEXT, "*", ParType.ITALIC) { data ->
|
|
Text(data[ParType.TEXT]!!)
|
|
},
|
|
ParState(ParType.ITALIC, "*", ParType.TEXT) { data ->
|
|
Italic(data[ParType.ITALIC]!!)
|
|
},
|
|
|
|
// Autolinks
|
|
ParState(ParType.TEXT, "<", ParType.AUTOLINK) { data ->
|
|
Text(data[ParType.TEXT]!!)
|
|
},
|
|
ParState(ParType.AUTOLINK, ">", ParType.TEXT) { data ->
|
|
val content = data[ParType.AUTOLINK]!!
|
|
|
|
if (content.contains("@") and content.contains(".")) {
|
|
Link("mailto:$content", content)
|
|
} else {
|
|
Link(content, content)
|
|
}
|
|
},
|
|
|
|
)
|
|
|
|
private fun String.test(index: Int, value: String): Boolean {
|
|
return this.length >= index + value.length && this.substring(index, index + value.length) == value
|
|
}
|
|
|
|
fun parseParagraph(text: String): MarkdownPart.Paragraph {
|
|
val result = mutableListOf<MarkdownPart.ParagraphPart>()
|
|
val buffer = StringBuilder()
|
|
var type = ParType.TEXT
|
|
val data: ParagraphData = mutableMapOf()
|
|
var index = 0
|
|
var activeStates = states.filter { it.fromType == type }
|
|
var escaped = false
|
|
|
|
while (index < text.length) {
|
|
var found = false
|
|
val ch = text[index]
|
|
if (!escaped && ch == '\\') {
|
|
escaped = true
|
|
index++
|
|
continue
|
|
} else if (escaped) {
|
|
escaped = false
|
|
buffer.append(ch)
|
|
index++
|
|
continue
|
|
}
|
|
|
|
for (state in activeStates) {
|
|
if (state.fromType == type && text.test(index, state.text)) {
|
|
data[state.fromType] = buffer.toString()
|
|
buffer.clear()
|
|
state.out(data)?.let {
|
|
if (it !is Text || it.text.isNotBlank()) {
|
|
result.add(it)
|
|
}
|
|
}
|
|
type = state.toType
|
|
index += state.text.length
|
|
found = true
|
|
activeStates = states.filter { it.fromType == type }
|
|
break
|
|
}
|
|
}
|
|
if (!found) {
|
|
val ch = text[index]
|
|
if (ch == '\n') {
|
|
// Markdown hard line break: two or more spaces at end of line
|
|
if (buffer.length >= 2 && buffer.endsWith(" ")) {
|
|
val textBefore = buffer.substring(0, buffer.length - 2)
|
|
if (textBefore.isNotEmpty()) {
|
|
result.add(Text(textBefore))
|
|
}
|
|
result.add(LineBreak)
|
|
buffer.clear()
|
|
} else {
|
|
// Keep original behavior for soft breaks (collapse later in HTML)
|
|
buffer.append(ch)
|
|
}
|
|
} else {
|
|
buffer.append(ch)
|
|
}
|
|
index++
|
|
}
|
|
}
|
|
|
|
if (buffer.isNotEmpty()) {
|
|
result.add(Text(buffer.toString()))
|
|
}
|
|
|
|
return MarkdownPart.Paragraph(result)
|
|
}
|