Compare commits

...

2 Commits

5 changed files with 224 additions and 48 deletions

1
.gitignore vendored
View File

@@ -45,3 +45,4 @@ bin/
.kotlin .kotlin
kotlin-js-store kotlin-js-store
gradle.properties gradle.properties
.idea

View File

@@ -8,7 +8,7 @@ plugins {
} }
group = "nl.astraeus" group = "nl.astraeus"
version = "1.1.0" version = "2.0.0"
repositories { repositories {
mavenCentral() mavenCentral()

View File

@@ -1,9 +1,16 @@
package nl.astraeus.markdown.parser package nl.astraeus.markdown.parser
data class ListItem(
val text: String,
val order: Int,
val indentation: Int = 0
)
data class CheckboxItem( data class CheckboxItem(
val line: Int, val line: Int,
val checked: Boolean, val checked: Boolean,
val text: String, val text: String,
val indentation: Int = 0
) )
sealed class MarkdownPart { sealed class MarkdownPart {
@@ -66,7 +73,7 @@ sealed class MarkdownPart {
) : MarkdownPart() ) : MarkdownPart()
data class UnorderedList( data class UnorderedList(
val lines: List<Pair<Int, String>>, val lines: List<ListItem>,
) : MarkdownPart() ) : MarkdownPart()
data class CheckboxList( data class CheckboxList(
@@ -74,7 +81,7 @@ sealed class MarkdownPart {
) : MarkdownPart() ) : MarkdownPart()
data class OrderedList( data class OrderedList(
val lines: List<String>, val lines: List<ListItem>,
) : MarkdownPart() ) : MarkdownPart()
data class CodeBlock( data class CodeBlock(

View File

@@ -11,17 +11,22 @@ enum class MarkdownType {
} }
fun markdown(text: String): List<MarkdownPart> { fun markdown(text: String): List<MarkdownPart> {
val lines = text.lines() val lines: MutableList<String> = text.lines().toMutableList()
val parts = mutableListOf<MarkdownPart>() val parts = mutableListOf<MarkdownPart>()
var language = "" var language = ""
var type = MarkdownType.PARAGRAPH var type = MarkdownType.PARAGRAPH
var listIndex = 1 var listIndex = 1
var checkboxLine = 0 var checkboxLine = 0
var indentation = 0
var index = 0 var index = 0
val buffer = StringBuilder() val buffer = StringBuilder()
var checkboxList = mutableListOf<CheckboxItem>() var checkboxList = mutableListOf<CheckboxItem>()
if (!lines.isEmpty() && lines.last().isNotEmpty()) {
lines.add("")
}
fun parseBuffer() { fun parseBuffer() {
if (buffer.isNotBlank()) { if (buffer.isNotBlank()) {
parts.addAll(handleBuffer(type, buffer.toString(), language)) parts.addAll(handleBuffer(type, buffer.toString(), language))
@@ -46,7 +51,7 @@ fun markdown(text: String): List<MarkdownPart> {
|| line.startsWith("#.") || line.startsWith("#.")
) { ) {
buffer.append("\n") buffer.append("\n")
buffer.append(line.substring(2)) buffer.append(rawLine)
} else { } else {
buffer.append(" ") buffer.append(" ")
buffer.append(line) buffer.append(line)
@@ -56,7 +61,7 @@ fun markdown(text: String): List<MarkdownPart> {
type == MarkdownType.CHECKBOX_LIST -> { type == MarkdownType.CHECKBOX_LIST -> {
if (line.isBlank()) { if (line.isBlank()) {
if (buffer.isNotBlank()) { if (buffer.isNotBlank()) {
addCheckbox(checkboxList, checkboxLine, buffer) addCheckbox(checkboxList, checkboxLine, buffer, indentation)
} }
parts.add(MarkdownPart.CheckboxList(checkboxList)) parts.add(MarkdownPart.CheckboxList(checkboxList))
checkboxList = mutableListOf() checkboxList = mutableListOf()
@@ -64,10 +69,11 @@ fun markdown(text: String): List<MarkdownPart> {
continue continue
} else if (line.startsWith("- [ ]") || line.startsWith("- [x]")) { } else if (line.startsWith("- [ ]") || line.startsWith("- [x]")) {
if (buffer.isNotBlank()) { if (buffer.isNotBlank()) {
addCheckbox(checkboxList, checkboxLine, buffer) addCheckbox(checkboxList, checkboxLine, buffer, indentation)
} }
checkboxLine = index checkboxLine = index
buffer.append(line) buffer.append(line)
indentation = rawLine.countSpaces()
} else { } else {
buffer.append(" ") buffer.append(" ")
buffer.append(line) buffer.append(line)
@@ -133,7 +139,7 @@ fun markdown(text: String): List<MarkdownPart> {
parseBuffer() parseBuffer()
type = MarkdownType.ORDERED_LIST type = MarkdownType.ORDERED_LIST
listIndex = 2 listIndex = 2
buffer.append(line.substring(2)) buffer.append(rawLine)
} }
line.startsWith("- [ ]") || line.startsWith("- [x]") -> { line.startsWith("- [ ]") || line.startsWith("- [x]") -> {
@@ -141,6 +147,7 @@ fun markdown(text: String): List<MarkdownPart> {
type = MarkdownType.CHECKBOX_LIST type = MarkdownType.CHECKBOX_LIST
checkboxLine = index checkboxLine = index
buffer.append(line) buffer.append(line)
indentation = rawLine.countSpaces()
} }
line.startsWith("- ") || line.startsWith("* ") -> { line.startsWith("- ") || line.startsWith("* ") -> {
@@ -189,14 +196,14 @@ fun markdown(text: String): List<MarkdownPart> {
index++ index++
} }
if (type == MarkdownType.CHECKBOX_LIST) { /* if (type == MarkdownType.CHECKBOX_LIST) {
if (buffer.isNotBlank()) { if (buffer.isNotBlank()) {
addCheckbox(checkboxList, checkboxLine, buffer) addCheckbox(checkboxList, checkboxLine, buffer, indentation)
} }
parts.add(MarkdownPart.CheckboxList(checkboxList)) parts.add(MarkdownPart.CheckboxList(checkboxList))
} else { } else {
parseBuffer() parseBuffer()
} }*/
return parts return parts
} }
@@ -204,14 +211,16 @@ fun markdown(text: String): List<MarkdownPart> {
private fun addCheckbox( private fun addCheckbox(
checkboxList: MutableList<CheckboxItem>, checkboxList: MutableList<CheckboxItem>,
index: Int, index: Int,
buffer: StringBuilder buffer: StringBuilder,
indentation: Int = 0
) { ) {
if (buffer.length >= 5) { if (buffer.length >= 5) {
checkboxList.add( checkboxList.add(
CheckboxItem( CheckboxItem(
index, index,
buffer.startsWith("- [x]"), buffer.startsWith("- [x]"),
buffer.substring(5).trim() buffer.substring(5).trim(),
indentation
) )
) )
} else { } else {
@@ -248,17 +257,20 @@ private fun handleBuffer(
} }
MarkdownType.ORDERED_LIST -> { MarkdownType.ORDERED_LIST -> {
listOf(MarkdownPart.OrderedList(text.lines())) val lines = text.lines()
val list = mutableListOf<ListItem>()
parseList(lines, list)
listOf(MarkdownPart.OrderedList(list))
} }
MarkdownType.UNORDERED_LIST -> { MarkdownType.UNORDERED_LIST -> {
val lines = text.lines() val lines = text.lines()
val list = mutableListOf<Pair<Int, String>>() val list = mutableListOf<ListItem>()
parseList(lines, list)
for (line in lines) {
val indent: Int = line.countSpaces()
list.add((indent / 2) to line.substring(indent + 2))
}
listOf(MarkdownPart.UnorderedList(list)) listOf(MarkdownPart.UnorderedList(list))
} }
@@ -270,3 +282,23 @@ private fun handleBuffer(
parseTable(text) parseTable(text)
} }
} }
private fun parseList(
lines: List<String>,
list: MutableList<ListItem>
) {
val orderMap = mutableMapOf<Int, Int>()
for (line in lines) {
val indent: Int = line.countSpaces()
val order = orderMap.getOrPut(indent) { 1 }
list.add(
ListItem(
line.substring(indent + 2),
order,
indent
)
)
orderMap[indent] = order + 1
}
}

View File

@@ -1,6 +1,7 @@
package nl.astraeus.markdown.parser package nl.astraeus.markdown.parser
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals
class ParseTest { class ParseTest {
@@ -19,7 +20,25 @@ class ParseTest {
val md = markdown(input) val md = markdown(input)
printMarkdownParts(md) assertEquals(3, md.size)
val p1 = md[0] as MarkdownPart.Paragraph
assertEquals("Dit is een ", (p1.parts[0] as MarkdownPart.ParagraphPart.Text).text)
assertEquals("test", (p1.parts[1] as MarkdownPart.ParagraphPart.Bold).text)
assertEquals(", laat ", (p1.parts[2] as MarkdownPart.ParagraphPart.Text).text)
assertEquals("mij", (p1.parts[3] as MarkdownPart.ParagraphPart.BoldItalic).text)
assertEquals(" maar eens zien!\n\n", (p1.parts[4] as MarkdownPart.ParagraphPart.Text).text)
val p2 = md[1] as MarkdownPart.Paragraph
assertEquals("link: ", (p2.parts[0] as MarkdownPart.ParagraphPart.Text).text)
val link = p2.parts[1] as MarkdownPart.ParagraphPart.Link
assertEquals("www.nos.nl ", link.url)
assertEquals("NOS", link.label)
assertEquals("Nos title", link.title)
assertEquals(" of zo.\n\n", (p2.parts[2] as MarkdownPart.ParagraphPart.Text).text)
val l1 = md[2] as MarkdownPart.UnorderedList
assertEquals(1, l1.lines.size)
assertEquals("link: [NU](www.nu.nl \"Nu site\") of zo.", l1.lines[0].text)
} }
@Test @Test
@@ -31,7 +50,11 @@ class ParseTest {
val md = markdown(input) val md = markdown(input)
printMarkdownParts(md) assertEquals(1, md.size)
val p = md[0] as MarkdownPart.Paragraph
val img = p.parts[0] as MarkdownPart.ParagraphPart.Image
assertEquals("test2", img.alt)
assertEquals("https://upload.wikimedia.org/wikipedia/commons.png", img.src)
} }
@Test @Test
@@ -47,7 +70,10 @@ class ParseTest {
val md = markdown(input) val md = markdown(input)
printMarkdownParts(md) assertEquals(3, md.size)
assertEquals("Dit is een text\n\n", (md[0] as MarkdownPart.Paragraph).parts[0].let { (it as MarkdownPart.ParagraphPart.Text).text })
assertEquals("Code block\nCode block\n\n", (md[1] as MarkdownPart.CodeBlock).text)
assertEquals("Meer text\n\n", (md[2] as MarkdownPart.Paragraph).parts[0].let { (it as MarkdownPart.ParagraphPart.Text).text })
} }
@Test @Test
@@ -65,7 +91,40 @@ class ParseTest {
val md = markdown(input) val md = markdown(input)
printMarkdownParts(md) assertEquals(3, md.size)
val list = md[1] as MarkdownPart.UnorderedList
assertEquals(2, list.lines.size)
assertEquals("First More text", list.lines[0].text)
assertEquals("Second More text", list.lines[1].text)
assertEquals("Another paragraph\n\n", (md[2] as MarkdownPart.Paragraph).parts[0].let { (it as MarkdownPart.ParagraphPart.Text).text })
}
@Test
fun testUnorderedList2() {
val input = """
- First
- Second
- 2.1
- 2.1.1
- 2.1.1
- 3
""".trimIndent()
val md = markdown(input)
assertEquals(1, md.size)
val list = md[0] as MarkdownPart.UnorderedList
assertEquals(6, list.lines.size)
assertEquals("First", list.lines[0].text)
assertEquals(0, list.lines[0].indentation)
assertEquals("Second", list.lines[1].text)
assertEquals("2.1", list.lines[2].text)
assertEquals(2, list.lines[2].indentation)
assertEquals("2.1.1", list.lines[3].text)
assertEquals(4, list.lines[3].indentation)
assertEquals("2.1.1", list.lines[4].text)
assertEquals(6, list.lines[4].indentation)
assertEquals("3", list.lines[5].text)
} }
@Test @Test
@@ -83,7 +142,11 @@ class ParseTest {
val md = markdown(input) val md = markdown(input)
printMarkdownParts(md) assertEquals(3, md.size)
val list = md[1] as MarkdownPart.UnorderedList
assertEquals(2, list.lines.size)
assertEquals("First More text", list.lines[0].text)
assertEquals("Second More text", list.lines[1].text)
} }
@Test @Test
@@ -103,7 +166,15 @@ class ParseTest {
val md = markdown(input) val md = markdown(input)
printMarkdownParts(md) assertEquals(3, md.size)
val list = md[1] as MarkdownPart.UnorderedList
assertEquals(4, list.lines.size)
assertEquals("First More text", list.lines[0].text)
assertEquals("Second More text", list.lines[1].text)
assertEquals("Sub 1", list.lines[2].text)
assertEquals(2, list.lines[2].indentation)
assertEquals("Sub 2", list.lines[3].text)
assertEquals(2, list.lines[3].indentation)
} }
@Test @Test
@@ -115,13 +186,21 @@ class ParseTest {
More text More text
-. Second -. Second
More text More text
-. Sub 1
-. Sub 2
-. Third
Another paragraph Another paragraph
""".trimIndent() """.trimIndent()
val md = markdown(input) val md = markdown(input)
printMarkdownParts(md) assertEquals(3, md.size)
val list = md[1] as MarkdownPart.OrderedList
assertEquals(5, list.lines.size)
assertEquals(" First More text", list.lines[0].text)
assertEquals(" Sub 1", list.lines[2].text)
assertEquals(2, list.lines[2].indentation)
} }
@Test @Test
@@ -139,7 +218,11 @@ class ParseTest {
val md = markdown(input) val md = markdown(input)
printMarkdownParts(md) assertEquals(3, md.size)
val list = md[1] as MarkdownPart.OrderedList
assertEquals(2, list.lines.size)
assertEquals("First More text", list.lines[0].text)
assertEquals("Second More text", list.lines[1].text)
} }
@Test @Test
@@ -158,7 +241,15 @@ class ParseTest {
val md = markdown(input) val md = markdown(input)
printMarkdownParts(md) assertEquals(3, md.size)
val list = md[1] as MarkdownPart.CheckboxList
assertEquals(3, list.lines.size)
assertEquals(false, list.lines[0].checked)
assertEquals("Not checked, with some more text here", list.lines[0].text)
assertEquals(true, list.lines[1].checked)
assertEquals("Checked!", list.lines[1].text)
assertEquals(true, list.lines[2].checked)
assertEquals("Checked, text it!", list.lines[2].text)
} }
@Test @Test
@@ -173,7 +264,12 @@ class ParseTest {
val md = markdown(input) val md = markdown(input)
printMarkdownParts(md) assertEquals(2, md.size)
assertEquals("Todo", (md[0] as MarkdownPart.Header).text)
val list = md[1] as MarkdownPart.CheckboxList
assertEquals(2, list.lines.size)
assertEquals(false, list.lines[0].checked)
assertEquals(true, list.lines[1].checked)
} }
@Test @Test
@@ -201,7 +297,49 @@ class ParseTest {
val md = markdown(input) val md = markdown(input)
printMarkdownParts(md) assertEquals(5, md.size)
assertEquals(MarkdownPart.CheckboxList::class, md[1]::class)
assertEquals(MarkdownPart.CheckboxList::class, md[3]::class)
val list2 = md[3] as MarkdownPart.CheckboxList
assertEquals(2, list2.lines[0].indentation)
}
@Test
fun testCheckboxList4() {
val input = """
Dit is een text
- [ ] Not checked,
with some more text here
- [x] Checked!
- [x] Checked,
text it!
Nog 1:
- [ ] Not checked,
with some more text here
- [x] Checked!
- [x] Checked,
text it!
Meer text
""".trimIndent()
val md = markdown(input)
assertEquals(5, md.size)
val list1 = md[1] as MarkdownPart.CheckboxList
assertEquals(3, list1.lines.size)
assertEquals(0, list1.lines[0].indentation)
assertEquals(0, list1.lines[1].indentation)
assertEquals(2, list1.lines[2].indentation)
val list2 = md[3] as MarkdownPart.CheckboxList
assertEquals(3, list2.lines.size)
assertEquals(2, list2.lines[0].indentation)
assertEquals(4, list2.lines[1].indentation)
assertEquals(2, list2.lines[2].indentation)
} }
@@ -215,7 +353,11 @@ class ParseTest {
val md = markdown(input) val md = markdown(input)
printMarkdownParts(md) assertEquals(2, md.size)
val list = md[1] as MarkdownPart.CheckboxList
assertEquals(2, list.lines.size)
assertEquals(false, list.lines[0].checked)
assertEquals(true, list.lines[1].checked)
} }
@Test @Test
@@ -224,18 +366,12 @@ class ParseTest {
val md = markdown(input) val md = markdown(input)
printMarkdownParts(md) assertEquals(2, md.size)
assertEquals("Markdown", (md[0] as MarkdownPart.Header).text)
assertEquals(1, (md[0] as MarkdownPart.Header).size)
val p = md[1] as MarkdownPart.Paragraph
assertEquals("My ", (p.parts[0] as MarkdownPart.ParagraphPart.Text).text)
assertEquals("markdown", (p.parts[1] as MarkdownPart.ParagraphPart.Bold).text)
} }
private fun printMarkdownParts(md: List<MarkdownPart>) {
for (part in md) {
if (part is MarkdownPart.Paragraph) {
for (para in part.parts) {
println("PARA: ${para::class.simpleName} - ${para.toString().take(75)}")
}
} else {
println("PART: ${part::class.simpleName} - ${part.toString().take(75)}")
}
}
}
} }