• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download

<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 == "&amp;" || text == "&quot;" || text == "&lt;" || text == "&gt;") {
124                         return ContentEntity(text)
125                     }
126                     if (text == "&") {
127                         return ContentEntity("&amp;")
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