<lambda>null1 package org.jetbrains.dokka
2
3 import org.intellij.markdown.MarkdownElementTypes
4 import org.intellij.markdown.MarkdownTokenTypes
5 import org.intellij.markdown.html.entities.EntityConverter
6 import org.intellij.markdown.parser.LinkMap
7 import java.util.*
8
9 class LinkResolver(private val linkMap: LinkMap, private val contentFactory: (String) -> ContentBlock) {
10 fun getLinkInfo(refLabel: String) = linkMap.getLinkInfo(refLabel)
11 fun resolve(href: String): ContentBlock = contentFactory(href)
12 }
13
buildContentnull14 fun buildContent(tree: MarkdownNode, linkResolver: LinkResolver, inline: Boolean = false): MutableContent {
15 val result = MutableContent()
16 if (inline) {
17 buildInlineContentTo(tree, result, linkResolver)
18 } else {
19 buildContentTo(tree, result, linkResolver)
20 }
21 return result
22 }
23
buildContentTonull24 fun buildContentTo(tree: MarkdownNode, target: ContentBlock, linkResolver: LinkResolver) {
25 // println(tree.toTestString())
26 val nodeStack = ArrayDeque<ContentBlock>()
27 nodeStack.push(target)
28
29 tree.visit { node, processChildren ->
30 val parent = nodeStack.peek()
31
32 fun appendNodeWithChildren(content: ContentBlock) {
33 nodeStack.push(content)
34 processChildren()
35 parent.append(nodeStack.pop())
36 }
37
38 when (node.type) {
39 MarkdownElementTypes.ATX_1 -> appendNodeWithChildren(ContentHeading(1))
40 MarkdownElementTypes.ATX_2 -> appendNodeWithChildren(ContentHeading(2))
41 MarkdownElementTypes.ATX_3 -> appendNodeWithChildren(ContentHeading(3))
42 MarkdownElementTypes.ATX_4 -> appendNodeWithChildren(ContentHeading(4))
43 MarkdownElementTypes.ATX_5 -> appendNodeWithChildren(ContentHeading(5))
44 MarkdownElementTypes.ATX_6 -> appendNodeWithChildren(ContentHeading(6))
45 MarkdownElementTypes.UNORDERED_LIST -> appendNodeWithChildren(ContentUnorderedList())
46 MarkdownElementTypes.ORDERED_LIST -> appendNodeWithChildren(ContentOrderedList())
47 MarkdownElementTypes.LIST_ITEM -> appendNodeWithChildren(ContentListItem())
48 MarkdownElementTypes.EMPH -> appendNodeWithChildren(ContentEmphasis())
49 MarkdownElementTypes.STRONG -> appendNodeWithChildren(ContentStrong())
50 MarkdownElementTypes.CODE_SPAN -> {
51 val startDelimiter = node.child(MarkdownTokenTypes.BACKTICK)?.text
52 if (startDelimiter != null) {
53 val text = node.text.substring(startDelimiter.length).removeSuffix(startDelimiter)
54 val codeSpan = ContentCode().apply { append(ContentText(text)) }
55 parent.append(codeSpan)
56 }
57 }
58 MarkdownElementTypes.CODE_BLOCK,
59 MarkdownElementTypes.CODE_FENCE -> {
60 val language = node.child(MarkdownTokenTypes.FENCE_LANG)?.text?.trim() ?: ""
61 appendNodeWithChildren(ContentBlockCode(language))
62 }
63 MarkdownElementTypes.PARAGRAPH -> appendNodeWithChildren(ContentParagraph())
64
65 MarkdownElementTypes.INLINE_LINK -> {
66 val linkTextNode = node.child(MarkdownElementTypes.LINK_TEXT)
67 val destination = node.child(MarkdownElementTypes.LINK_DESTINATION)
68 if (linkTextNode != null) {
69 if (destination != null) {
70 val link = ContentExternalLink(destination.text)
71 renderLinkTextTo(linkTextNode, link, linkResolver)
72 parent.append(link)
73 } else {
74 val link = ContentExternalLink(linkTextNode.getLabelText())
75 renderLinkTextTo(linkTextNode, link, linkResolver)
76 parent.append(link)
77 }
78 }
79 }
80 MarkdownElementTypes.SHORT_REFERENCE_LINK,
81 MarkdownElementTypes.FULL_REFERENCE_LINK -> {
82 val labelElement = node.child(MarkdownElementTypes.LINK_LABEL)
83 if (labelElement != null) {
84 val linkInfo = linkResolver.getLinkInfo(labelElement.text)
85 val labelText = labelElement.getLabelText()
86 val link = linkInfo?.let { linkResolver.resolve(it.destination.toString()) } ?: linkResolver.resolve(labelText)
87 val linkText = node.child(MarkdownElementTypes.LINK_TEXT)
88 if (linkText != null) {
89 renderLinkTextTo(linkText, link, linkResolver)
90 } else {
91 link.append(ContentText(labelText))
92 }
93 parent.append(link)
94 }
95 }
96 MarkdownTokenTypes.WHITE_SPACE -> {
97 // Don't append first space if start of header (it is added during formatting later)
98 // v
99 // #### Some Heading
100 if (nodeStack.peek() !is ContentHeading || node.parent?.children?.first() != node) {
101 parent.append(ContentText(node.text))
102 }
103 }
104 MarkdownTokenTypes.EOL -> {
105 if ((keepEol(nodeStack.peek()) && node.parent?.children?.last() != node) ||
106 // Keep extra blank lines when processing lists (affects Markdown formatting)
107 (processingList(nodeStack.peek()) && node.previous?.type == MarkdownTokenTypes.EOL)) {
108 parent.append(ContentText(node.text))
109 }
110 }
111
112 MarkdownTokenTypes.CODE_LINE -> {
113 val content = ContentText(node.text)
114 if (parent is ContentBlockCode) {
115 parent.append(content)
116 } else {
117 parent.append(ContentBlockCode().apply { append(content) })
118 }
119 }
120
121 MarkdownTokenTypes.TEXT -> {
122 fun createEntityOrText(text: String): ContentNode {
123 if (text == "&" || text == """ || text == "<" || text == ">") {
124 return ContentEntity(text)
125 }
126 if (text == "&") {
127 return ContentEntity("&")
128 }
129 val decodedText = EntityConverter.replaceEntities(text, true, true)
130 if (decodedText != text) {
131 return ContentEntity(text)
132 }
133 return ContentText(text)
134 }
135
136 parent.append(createEntityOrText(node.text))
137 }
138
139 MarkdownTokenTypes.EMPH -> {
140 val parentNodeType = node.parent?.type
141 if (parentNodeType != MarkdownElementTypes.EMPH && parentNodeType != MarkdownElementTypes.STRONG) {
142 parent.append(ContentText(node.text))
143 }
144 }
145
146 MarkdownTokenTypes.COLON,
147 MarkdownTokenTypes.SINGLE_QUOTE,
148 MarkdownTokenTypes.DOUBLE_QUOTE,
149 MarkdownTokenTypes.LT,
150 MarkdownTokenTypes.GT,
151 MarkdownTokenTypes.LPAREN,
152 MarkdownTokenTypes.RPAREN,
153 MarkdownTokenTypes.LBRACKET,
154 MarkdownTokenTypes.RBRACKET,
155 MarkdownTokenTypes.EXCLAMATION_MARK,
156 MarkdownTokenTypes.BACKTICK,
157 MarkdownTokenTypes.CODE_FENCE_CONTENT -> {
158 parent.append(ContentText(node.text))
159 }
160
161 MarkdownElementTypes.LINK_DEFINITION -> {
162 }
163
164 else -> {
165 processChildren()
166 }
167 }
168 }
169 }
170
<lambda>null171 private fun MarkdownNode.getLabelText() = children.filter { it.type == MarkdownTokenTypes.TEXT || it.type == MarkdownTokenTypes.EMPH }.joinToString("") { it.text }
172
keepEolnull173 private fun keepEol(node: ContentNode) = node is ContentParagraph || node is ContentSection || node is ContentBlockCode
174 private fun processingList(node: ContentNode) = node is ContentOrderedList || node is ContentUnorderedList
175
176 fun buildInlineContentTo(tree: MarkdownNode, target: ContentBlock, linkResolver: LinkResolver) {
177 val inlineContent = tree.children.singleOrNull { it.type == MarkdownElementTypes.PARAGRAPH }?.children ?: listOf(tree)
178 inlineContent.forEach {
179 buildContentTo(it, target, linkResolver)
180 }
181 }
182
renderLinkTextTonull183 fun renderLinkTextTo(tree: MarkdownNode, target: ContentBlock, linkResolver: LinkResolver) {
184 val linkTextNodes = tree.children.drop(1).dropLast(1)
185 linkTextNodes.forEach {
186 buildContentTo(it, target, linkResolver)
187 }
188 }
189