diff --git a/.idea/artifacts/markdown_parser_js_1_0_1.xml b/.idea/artifacts/markdown_parser_js_1_0_1.xml
new file mode 100644
index 0000000..2e75872
--- /dev/null
+++ b/.idea/artifacts/markdown_parser_js_1_0_1.xml
@@ -0,0 +1,8 @@
+
+
+ $PROJECT_DIR$/build/libs
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/artifacts/markdown_parser_js_1_0_2.xml b/.idea/artifacts/markdown_parser_js_1_0_2.xml
new file mode 100644
index 0000000..c779ae4
--- /dev/null
+++ b/.idea/artifacts/markdown_parser_js_1_0_2.xml
@@ -0,0 +1,8 @@
+
+
+ $PROJECT_DIR$/build/libs
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/artifacts/markdown_parser_js_1_0_3.xml b/.idea/artifacts/markdown_parser_js_1_0_3.xml
new file mode 100644
index 0000000..692f65a
--- /dev/null
+++ b/.idea/artifacts/markdown_parser_js_1_0_3.xml
@@ -0,0 +1,8 @@
+
+
+ $PROJECT_DIR$/build/libs
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/artifacts/markdown_parser_js_1_0_4_SNAPSHOT.xml b/.idea/artifacts/markdown_parser_js_1_0_4_SNAPSHOT.xml
new file mode 100644
index 0000000..0edbfd2
--- /dev/null
+++ b/.idea/artifacts/markdown_parser_js_1_0_4_SNAPSHOT.xml
@@ -0,0 +1,8 @@
+
+
+ $PROJECT_DIR$/build/libs
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/artifacts/markdown_parser_jvm_1_0_1.xml b/.idea/artifacts/markdown_parser_jvm_1_0_1.xml
new file mode 100644
index 0000000..c448d28
--- /dev/null
+++ b/.idea/artifacts/markdown_parser_jvm_1_0_1.xml
@@ -0,0 +1,8 @@
+
+
+ $PROJECT_DIR$/build/libs
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/artifacts/markdown_parser_jvm_1_0_2.xml b/.idea/artifacts/markdown_parser_jvm_1_0_2.xml
new file mode 100644
index 0000000..6648ff9
--- /dev/null
+++ b/.idea/artifacts/markdown_parser_jvm_1_0_2.xml
@@ -0,0 +1,8 @@
+
+
+ $PROJECT_DIR$/build/libs
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/artifacts/markdown_parser_jvm_1_0_3.xml b/.idea/artifacts/markdown_parser_jvm_1_0_3.xml
new file mode 100644
index 0000000..6fb99b3
--- /dev/null
+++ b/.idea/artifacts/markdown_parser_jvm_1_0_3.xml
@@ -0,0 +1,8 @@
+
+
+ $PROJECT_DIR$/build/libs
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/artifacts/markdown_parser_jvm_1_0_4_SNAPSHOT.xml b/.idea/artifacts/markdown_parser_jvm_1_0_4_SNAPSHOT.xml
new file mode 100644
index 0000000..84fad01
--- /dev/null
+++ b/.idea/artifacts/markdown_parser_jvm_1_0_4_SNAPSHOT.xml
@@ -0,0 +1,8 @@
+
+
+ $PROJECT_DIR$/build/libs
+
+
+
+
+
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index eab4917..5800fb7 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -8,7 +8,7 @@ plugins {
}
group = "nl.astraeus"
-version = "1.0.3"
+version = "1.0.4"
repositories {
mavenCentral()
diff --git a/src/commonMain/kotlin/nl/astraeus/markdown/parser/Markdown.kt b/src/commonMain/kotlin/nl/astraeus/markdown/parser/Markdown.kt
index d4d9d49..3a319a5 100644
--- a/src/commonMain/kotlin/nl/astraeus/markdown/parser/Markdown.kt
+++ b/src/commonMain/kotlin/nl/astraeus/markdown/parser/Markdown.kt
@@ -1,12 +1,18 @@
package nl.astraeus.markdown.parser
+data class CheckboxItem(
+ val line: Int,
+ val checked: Boolean,
+ val text: String,
+)
+
sealed class MarkdownPart {
data object NewLine : MarkdownPart()
data object PageBreak : MarkdownPart()
- sealed class ParagraphPart() {
+ sealed class ParagraphPart {
data class Text(
val text: String
) : ParagraphPart()
@@ -44,6 +50,10 @@ sealed class MarkdownPart {
class InlineCode(
val text: String
) : ParagraphPart()
+
+ data class IndentedCode(
+ val code: String
+ ) : ParagraphPart()
}
data class Paragraph(
@@ -59,6 +69,10 @@ sealed class MarkdownPart {
val lines: List,
) : MarkdownPart()
+ data class CheckboxList(
+ val lines: List,
+ ) : MarkdownPart()
+
data class OrderedList(
val lines: List,
) : MarkdownPart()
diff --git a/src/commonMain/kotlin/nl/astraeus/markdown/parser/Paragraph.kt b/src/commonMain/kotlin/nl/astraeus/markdown/parser/Paragraph.kt
index 4bb4430..01c773c 100644
--- a/src/commonMain/kotlin/nl/astraeus/markdown/parser/Paragraph.kt
+++ b/src/commonMain/kotlin/nl/astraeus/markdown/parser/Paragraph.kt
@@ -4,6 +4,7 @@ import nl.astraeus.markdown.parser.MarkdownPart.ParagraphPart.*
private enum class ParType {
TEXT,
+ CODE,
LINK_LABEL,
LINK_URL,
LINK_TITLE,
@@ -30,6 +31,14 @@ private data class ParState(
)
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]!!)
@@ -103,14 +112,7 @@ private val states = listOf(
Text(data[ParType.TEXT]!!)
},
ParState(ParType.ITALIC, "*", ParType.TEXT) { data ->
- BoldItalic(data[ParType.ITALIC]!!)
- },
-
- ParState(ParType.TEXT, "`", ParType.INLINE_CODE) { data ->
- Text(data[ParType.TEXT]!!)
- },
- ParState(ParType.INLINE_CODE, "`", ParType.TEXT) { data ->
- InlineCode(data[ParType.INLINE_CODE]!!)
+ Italic(data[ParType.ITALIC]!!)
},
)
@@ -125,9 +127,22 @@ fun parseParagraph(text: String): MarkdownPart.Paragraph {
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()
diff --git a/src/commonMain/kotlin/nl/astraeus/markdown/parser/Parser.kt b/src/commonMain/kotlin/nl/astraeus/markdown/parser/Parser.kt
index 70f5574..9e9bae6 100644
--- a/src/commonMain/kotlin/nl/astraeus/markdown/parser/Parser.kt
+++ b/src/commonMain/kotlin/nl/astraeus/markdown/parser/Parser.kt
@@ -5,7 +5,9 @@ enum class MarkdownType {
PARAGRAPH,
ORDERED_LIST,
UNORDERED_LIST,
+ CHECKBOX_LIST,
TABLE,
+ INDENTED_CODE,
}
fun markdown(text: String): List {
@@ -17,6 +19,7 @@ fun markdown(text: String): List {
var index = 0
val buffer = StringBuilder()
+ val checkboxList = mutableListOf()
fun parseBuffer() {
if (buffer.isNotBlank()) {
@@ -33,7 +36,7 @@ fun markdown(text: String): List {
//println("BUFFER [${buffer.length}] TYPE ${type} \t LINE - ${line}")
when {
type == MarkdownType.ORDERED_LIST -> {
- if (!line.startsWith("${listIndex++}.")) {
+ if (!line.startsWith("${listIndex++}.") && !line.startsWith("-.")) {
parseBuffer()
continue
} else {
@@ -42,6 +45,22 @@ fun markdown(text: String): List {
}
}
+ type == MarkdownType.CHECKBOX_LIST -> {
+ if (!line.startsWith("- [ ]") && !line.startsWith("- [x]")) {
+ parts.add(MarkdownPart.CheckboxList(checkboxList))
+ parseBuffer()
+ continue
+ } else {
+ checkboxList.add(
+ CheckboxItem(
+ index,
+ line.startsWith("- [x]"),
+ line.substring(5).trim()
+ )
+ )
+ }
+ }
+
type == MarkdownType.UNORDERED_LIST -> {
if (!line.startsWith("- ") &&
!line.startsWith("* ")
@@ -69,6 +88,16 @@ fun markdown(text: String): List {
parseBuffer()
}
+ type == MarkdownType.INDENTED_CODE -> {
+ if (!rawLine.startsWith(" ")) {
+ parseBuffer()
+ continue
+ } else {
+ buffer.append(line)
+ buffer.append("\n")
+ }
+ }
+
line.startsWith("```") -> {
if (type != MarkdownType.CODE) {
parseBuffer()
@@ -86,7 +115,7 @@ fun markdown(text: String): List {
continue
}
- line.startsWith("1.") -> {
+ line.startsWith("1.") || line.startsWith("-.") -> {
parseBuffer()
type = MarkdownType.ORDERED_LIST
listIndex = 2
@@ -94,6 +123,18 @@ fun markdown(text: String): List {
buffer.append("\n")
}
+ line.startsWith("- [ ]") || line.startsWith("- [x]") -> {
+ parseBuffer()
+ type = MarkdownType.CHECKBOX_LIST
+ checkboxList.add(
+ CheckboxItem(
+ index,
+ line.startsWith("- [x]"),
+ line.substring(5).trim()
+ )
+ )
+ }
+
line.startsWith("- ") || line.startsWith("* ") -> {
parseBuffer()
type = MarkdownType.UNORDERED_LIST
@@ -120,6 +161,13 @@ fun markdown(text: String): List {
parts.add(MarkdownPart.Header(headerText, headerLevel))
}
+ rawLine.startsWith(" ") -> {
+ parseBuffer()
+ type = MarkdownType.INDENTED_CODE
+ buffer.append(line)
+ buffer.append("\n")
+ }
+
line == "[break]" -> {
parseBuffer()
parts.add(MarkdownPart.PageBreak)
@@ -148,6 +196,10 @@ private fun handleBuffer(
listOf(MarkdownPart.CodeBlock(text, language))
}
+ MarkdownType.INDENTED_CODE -> {
+ listOf(MarkdownPart.CodeBlock(text, "block"))
+ }
+
MarkdownType.PARAGRAPH -> {
listOf(parseParagraph(text))
}
@@ -160,6 +212,10 @@ private fun handleBuffer(
listOf(MarkdownPart.UnorderedList(text.lines()))
}
+ MarkdownType.CHECKBOX_LIST -> {
+ error("Checkbox list is handled separately")
+ }
+
MarkdownType.TABLE -> {
parseTable(text)
}
diff --git a/src/commonTest/kotlin/nl/astraeus/markdown/parser/ParseTest.kt b/src/commonTest/kotlin/nl/astraeus/markdown/parser/ParseTest.kt
index 4388085..c2e8ce7 100644
--- a/src/commonTest/kotlin/nl/astraeus/markdown/parser/ParseTest.kt
+++ b/src/commonTest/kotlin/nl/astraeus/markdown/parser/ParseTest.kt
@@ -34,6 +34,38 @@ class ParseTest {
printMarkdownParts(md)
}
+ @Test
+ fun testIndentedCode() {
+ val input = """
+ Dit is een text
+
+ Code block
+ Code block
+
+ Meer text
+ """.trimIndent()
+
+ val md = markdown(input)
+
+ printMarkdownParts(md)
+ }
+
+ @Test
+ fun testCheckboxList() {
+ val input = """
+ Dit is een text
+
+ - [ ] Not checked
+ - [x] Checked
+
+ Meer text
+ """.trimIndent()
+
+ val md = markdown(input)
+
+ printMarkdownParts(md)
+ }
+
private fun printMarkdownParts(md: List) {
for (part in md) {
if (part is MarkdownPart.Paragraph) {
diff --git a/src/commonTest/kotlin/nl/astraeus/markdown/parser/TestParagraph.kt b/src/commonTest/kotlin/nl/astraeus/markdown/parser/TestParagraph.kt
index 8d3bb5b..ecafc81 100644
--- a/src/commonTest/kotlin/nl/astraeus/markdown/parser/TestParagraph.kt
+++ b/src/commonTest/kotlin/nl/astraeus/markdown/parser/TestParagraph.kt
@@ -19,6 +19,31 @@ class TestParagraph {
assertTrue(result.parts[2] is MarkdownPart.ParagraphPart.Text)
}
+ @Test
+ fun testCode() {
+ val input = "Text `code` Text"
+
+ val result = parseParagraph(input)
+
+ assertEquals(3, result.parts.size)
+
+ assertTrue(result.parts[0] is MarkdownPart.ParagraphPart.Text)
+ assertTrue(result.parts[1] is MarkdownPart.ParagraphPart.InlineCode)
+ assertTrue(result.parts[2] is MarkdownPart.ParagraphPart.Text)
+ }
+
+ @Test
+ fun testIndentedCode() {
+ val input = "Text \n code\n line 2\n\nText"
+
+ val result = parseParagraph(input)
+
+ assertEquals(3, result.parts.size)
+
+ assertTrue(result.parts[0] is MarkdownPart.ParagraphPart.Text)
+ assertTrue(result.parts[1] is MarkdownPart.ParagraphPart.IndentedCode)
+ assertTrue(result.parts[2] is MarkdownPart.ParagraphPart.Text)
+ }
@Test
fun testLink() {
@@ -30,4 +55,28 @@ class TestParagraph {
assertTrue(result.parts[0] is MarkdownPart.ParagraphPart.Link)
}
+
+ @Test
+ fun testEscaped() {
+ val input = "Test \\`escaped\\` text"
+
+ val result = parseParagraph(input)
+
+ assertEquals(1, result.parts.size)
+
+ assertTrue(result.parts[0] is MarkdownPart.ParagraphPart.Text)
+ }
+
+
+ @Test
+ fun testFormatting() {
+ val input = "test **bold**, *italic*, ***bold and italic***, ~~strikethrough~~, and `code`."
+
+ val result = parseParagraph(input)
+
+ assertEquals(1, result.parts.size)
+
+ assertTrue(result.parts[0] is MarkdownPart.ParagraphPart.Text)
+ }
+
}