aboutsummaryrefslogtreecommitdiff
path: root/src/Markdown/MarkdownProcessor.kt
blob: 040f2ad5238a3d40d26ec5619ff361b6a1eabb1b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package org.jetbrains.dokka

import org.jetbrains.markdown.*
import com.intellij.lang.impl.PsiBuilderImpl
import com.intellij.psi.tree.TokenSet
import com.intellij.lang.Language
import com.intellij.psi.tree.IFileElementType
import com.intellij.lang.LighterASTNode
import com.intellij.util.diff.FlyweightCapableTreeStructure
import com.intellij.openapi.util.Ref
import org.jetbrains.markdown.lexer.MarkdownLexer
import com.intellij.psi.tree.IElementType

public object MarkdownProcessor {
    val EXPR_LANGUAGE = object : Language("MARKDOWN") {}
    val DOCUMENT = IFileElementType("DOCUMENT", EXPR_LANGUAGE);

    public fun parse(markdown: String): MarkdownTree {
        val parser = MarkdownParser()
        val builder = PsiBuilderImpl(null, null, TokenSet.EMPTY, TokenSet.EMPTY, MarkdownLexer(), null, markdown, null, null)
        parser.parse_only_(DOCUMENT, builder)
        val light = builder.getLightTree()!!
        return MarkdownTree(markdown, light)
    }
}

public class MarkdownTree(private val text: String, private val structure: FlyweightCapableTreeStructure<LighterASTNode>) {
    fun visit(action: (LighterASTNode, String, visitChildren: () -> Unit) -> Unit) {
        visit(structure.getRoot(), action)
    }

    fun findChildByType(node: LighterASTNode, findType: IElementType) : LighterASTNode? {
        val ref: Ref<Array<LighterASTNode>?> = Ref.create<Array<LighterASTNode>?>()
        val count = structure.getChildren(node, ref)
        val children = ref.get()
        if (children != null) {
            for (index in 0..count - 1) {
                val child = children[index]
                val nodeType = child.getTokenType()
                if (nodeType == findType)
                    return child
                val nestedChild = findChildByType(child, findType)
                if (nestedChild != null)
                    return nestedChild
            }
        }
        return null
    }

    fun getNodeText(node: LighterASTNode) : String {
        return text.substring(node.getStartOffset(), node.getEndOffset())
    }

    fun visit(node: LighterASTNode, action: (LighterASTNode, String, visitChildren: () -> Unit) -> Unit) {
        action(node, text) {
            val ref : Ref<Array<LighterASTNode>?> = Ref.create<Array<LighterASTNode>?>()
            val count = structure.getChildren(node, ref)
            val children = ref.get()
            if (children != null) {
                for (index in 0..count - 1) {
                    val child = children[index]
                    visit(child, action)
                }
            }
        }
    }

}

public fun MarkdownTree.toTestString(): String {
    val sb = StringBuilder()
    var level = 0
    visit {(node, text, visitChildren) ->
        val nodeText = text.substring(node.getStartOffset(), node.getEndOffset())
        sb.append(" ".repeat(level * 2))
        sb.append(node.getTokenType().toString())
        sb.append(":" + nodeText.replace("\n", "\u23CE"))
        sb.appendln()
        level++
        visitChildren()
        level--
    }
    return sb.toString()
}

public fun MarkdownTree.toHtml(): String {
    val sb = StringBuilder()
    visit {(node, text, processChildren) ->
        val nodeType = node.getTokenType()
        val nodeText = text.substring(node.getStartOffset(), node.getEndOffset())
        when (nodeType) {
            MarkdownElementTypes.BULLET_LIST -> {
                sb.appendln("<ul>")
                processChildren()
                sb.appendln("</ul>")
            }
            MarkdownElementTypes.HORIZONTAL_RULE -> {
                sb.appendln("<hr/>")
            }
            MarkdownElementTypes.ORDERED_LIST -> {
                sb.appendln("<ol>")
                processChildren()
                sb.appendln("</ol>")
            }
            MarkdownElementTypes.LIST_BLOCK -> {
                sb.append("<li>")
                processChildren()
                sb.appendln("</li>")
            }
            MarkdownElementTypes.EMPH -> {
                sb.append("<em>")
                processChildren()
                sb.append("</em>")
            }
            MarkdownElementTypes.STRONG -> {
                sb.append("<strong>")
                processChildren()
                sb.append("</strong>")
            }
            MarkdownElementTypes.PLAIN_TEXT -> {
                sb.append(nodeText)
            }
            MarkdownElementTypes.END_LINE -> {
                sb.appendln()
            }
            MarkdownElementTypes.BLANK_LINE -> {
                sb.appendln()
            }
            MarkdownElementTypes.PARA -> {
                sb.append("<p>")
                processChildren()
                sb.appendln("</p>")
            }
            else -> {
                processChildren()
            }
        }
    }
    return sb.toString()
}


fun markdownToHtml(markdown: String): String {
    val markdownTree = MarkdownProcessor.parse(markdown)
    val ast = markdownTree.toTestString()
    return markdownTree.toHtml()
}