All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.org.intellij.markdown.parser.LinkMap.kt Maven / Gradle / Ivy

There is a newer version: 0.7.3
Show newest version
package org.intellij.markdown.parser

import org.intellij.markdown.MarkdownElementTypes
import org.intellij.markdown.ast.ASTNode
import org.intellij.markdown.ast.accept
import org.intellij.markdown.ast.getTextInNode
import org.intellij.markdown.ast.visitors.RecursiveVisitor
import org.intellij.markdown.html.entities.EntityConverter
import org.intellij.markdown.html.urlEncode
import org.intellij.markdown.lexer.Compat.codePointToString
import org.intellij.markdown.lexer.Compat.forEachCodePoint
import kotlin.text.Regex

class LinkMap(private val map: Map) {
    fun getLinkInfo(label: CharSequence): LinkInfo? {
        return map[normalizeLabel(label)]
    }

    companion object Builder {
        fun normalizeLabel(label: CharSequence): CharSequence {
            return SPACES_REGEX.replace(label, " ").lowercase()
        }

        fun buildLinkMap(root: ASTNode, text: CharSequence): LinkMap {
            val map = HashMap()

            root.accept(object : RecursiveVisitor() {
                override fun visitNode(node: ASTNode) {
                    if (node.type == MarkdownElementTypes.LINK_DEFINITION) {
                        val linkLabel = normalizeLabel(
                            node.children.first { it.type == MarkdownElementTypes.LINK_LABEL }.getTextInNode(text)
                        )
                        if (!map.containsKey(linkLabel)) {
                            map[linkLabel] = LinkInfo.create(node, text)
                        }
                    } else {
                        super.visitNode(node)
                    }
                }
            })

            return LinkMap(map)
        }

        fun normalizeDestination(s: CharSequence, processEscapes: Boolean): CharSequence {
            val destination = EntityConverter.replaceEntities(clearBounding(s, "<>"), true, processEscapes)
            val sb = StringBuilder()
            destination.forEachCodePoint { code ->
                val c = code.toChar()
                if (code == 32) {
                    sb.append("%20")
                } else if (code < 32 || code >= 128 || "\".<>\\^_`{|}".contains(c)) {
                    sb.append(urlEncode(codePointToString(code)))
                } else {
                    sb.append(c)
                }
            }
            return sb.toString()
        }

        fun normalizeTitle(s: CharSequence): CharSequence =
            EntityConverter.replaceEntities(
                clearBounding(s, "\"\"", "''", "()"),
                processEntities = true,
                processEscapes = true
            )

        private fun clearBounding(s: CharSequence, vararg boundQuotes: String): CharSequence {
            if (s.isEmpty()) {
                return s
            }
            for (quotePair in boundQuotes) {
                if (s[0] == quotePair[0] && s[s.length - 1] == quotePair[1]) {
                    return s.subSequence(1, s.length - 1)
                }
            }
            return s
        }

        private val SPACES_REGEX = Regex("\\s+")
    }

    data class LinkInfo(val node: ASTNode, val destination: CharSequence, val title: CharSequence?) {
        companion object {
            fun create(node: ASTNode, fileText: CharSequence): LinkInfo {
                val destination: CharSequence = normalizeDestination(
                    node.children
                        .first { it.type == MarkdownElementTypes.LINK_DESTINATION }
                        .getTextInNode(fileText),
                    true)
                val title: CharSequence? = node.children.firstOrNull { it.type == MarkdownElementTypes.LINK_TITLE }
                    ?.getTextInNode(fileText)?.let { normalizeTitle(it) }
                return LinkInfo(node, destination, title)
            }
        }
    }
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy