Alternative way for link passing and substitution in markdown
This commit is contained in:
parent
0c4ae405b8
commit
324afe8fd5
@ -0,0 +1,94 @@
|
|||||||
|
package space.kscience.snark.html
|
||||||
|
|
||||||
|
import kotlinx.io.Source
|
||||||
|
import kotlinx.io.readString
|
||||||
|
import org.intellij.markdown.IElementType
|
||||||
|
import org.intellij.markdown.MarkdownElementTypes
|
||||||
|
import org.intellij.markdown.ast.ASTNode
|
||||||
|
import org.intellij.markdown.ast.findChildOfType
|
||||||
|
import org.intellij.markdown.ast.getTextInNode
|
||||||
|
import org.intellij.markdown.flavours.space.SFMFlavourDescriptor
|
||||||
|
import org.intellij.markdown.html.*
|
||||||
|
import org.intellij.markdown.parser.LinkMap
|
||||||
|
import org.intellij.markdown.parser.MarkdownParser
|
||||||
|
import space.kscience.snark.SnarkReader
|
||||||
|
|
||||||
|
private class SnarkInlineLinkGeneratingProvider(
|
||||||
|
baseURI: URI?,
|
||||||
|
resolveAnchors: Boolean = false,
|
||||||
|
) : LinkGeneratingProvider(baseURI, resolveAnchors) {
|
||||||
|
|
||||||
|
override fun getRenderInfo(text: String, node: ASTNode): RenderInfo? {
|
||||||
|
return RenderInfo(
|
||||||
|
label = node.findChildOfType(MarkdownElementTypes.LINK_TEXT) ?: return null,
|
||||||
|
destination = node.findChildOfType(MarkdownElementTypes.LINK_DESTINATION)?.getTextInNode(text)?.let { raw ->
|
||||||
|
val processedLink = WebPageTextProcessor.functionRegex.replace(raw) { match ->
|
||||||
|
when (match.groups["target"]?.value) {
|
||||||
|
"homeRef" -> "snark://homeRef"
|
||||||
|
"resolveRef" -> "snark://ref/${match.groups["name"]?.value ?: ""}"
|
||||||
|
"resolvePageRef" -> "snark://page/${match.groups["name"]?.value ?: ""}"
|
||||||
|
"pageMeta.get" -> "snark://meta/${match.groups["name"]?.value ?: ""}"
|
||||||
|
else -> match.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LinkMap.normalizeDestination(processedLink, true)
|
||||||
|
} ?: "",
|
||||||
|
title = node.findChildOfType(MarkdownElementTypes.LINK_TITLE)?.getTextInNode(text)?.let {
|
||||||
|
LinkMap.normalizeTitle(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SnarkImageGeneratingProvider(
|
||||||
|
linkMap: LinkMap,
|
||||||
|
baseURI: URI?,
|
||||||
|
) : ImageGeneratingProvider(linkMap, baseURI) {
|
||||||
|
|
||||||
|
val snarkInlineLinkProvider = SnarkInlineLinkGeneratingProvider(baseURI)
|
||||||
|
override fun getRenderInfo(text: String, node: ASTNode): RenderInfo? {
|
||||||
|
node.findChildOfType(MarkdownElementTypes.INLINE_LINK)?.let { linkNode ->
|
||||||
|
return snarkInlineLinkProvider.getRenderInfo(text, linkNode)
|
||||||
|
}
|
||||||
|
(node.findChildOfType(MarkdownElementTypes.FULL_REFERENCE_LINK)
|
||||||
|
?: node.findChildOfType(MarkdownElementTypes.SHORT_REFERENCE_LINK))
|
||||||
|
?.let { linkNode ->
|
||||||
|
return referenceLinkProvider.getRenderInfo(text, linkNode)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public object SnarkFlavorDescriptor : SFMFlavourDescriptor(false) {
|
||||||
|
override fun createHtmlGeneratingProviders(linkMap: LinkMap, baseURI: URI?): Map<IElementType, GeneratingProvider> {
|
||||||
|
return super.createHtmlGeneratingProviders(linkMap, baseURI) + mapOf(
|
||||||
|
MarkdownElementTypes.INLINE_LINK to SnarkInlineLinkGeneratingProvider(baseURI, absolutizeAnchorLinks)
|
||||||
|
.makeXssSafe(useSafeLinks),
|
||||||
|
MarkdownElementTypes.IMAGE to SnarkImageGeneratingProvider(linkMap, baseURI)
|
||||||
|
.makeXssSafe(useSafeLinks),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public object MarkdownReader : SnarkHtmlReader {
|
||||||
|
override val types: Set<String> = setOf("text/markdown", "md", "markdown")
|
||||||
|
|
||||||
|
override fun readFrom(source: String): PageFragment = PageFragment {
|
||||||
|
val parsedTree = markdownParser.parse(IElementType("ROOT"), source)
|
||||||
|
// markdownParser.buildMarkdownTreeFromString(source)
|
||||||
|
val htmlString = HtmlGenerator(source, parsedTree, markdownFlavor).generateHtml()
|
||||||
|
|
||||||
|
consumer.onTagContentUnsafe {
|
||||||
|
+htmlString
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private val markdownFlavor = SnarkFlavorDescriptor//SFMFlavourDescriptor(false)
|
||||||
|
private val markdownParser = MarkdownParser(markdownFlavor)
|
||||||
|
|
||||||
|
override fun readFrom(source: Source): PageFragment = readFrom(source.readString())
|
||||||
|
|
||||||
|
public val snarkReader: SnarkReader<PageFragment> = SnarkReader(this, "text/markdown")
|
||||||
|
|
||||||
|
}
|
@ -4,9 +4,9 @@ import kotlinx.html.*
|
|||||||
import space.kscience.dataforge.meta.string
|
import space.kscience.dataforge.meta.string
|
||||||
import space.kscience.dataforge.names.parseAsName
|
import space.kscience.dataforge.names.parseAsName
|
||||||
import space.kscience.snark.TextProcessor
|
import space.kscience.snark.TextProcessor
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
public class WebPageTextProcessor(private val page: PageContext) : TextProcessor {
|
public class WebPageTextProcessor(private val page: PageContext) : TextProcessor {
|
||||||
private val regex = """\$\{([\w.]*)(?>\("(.*)"\))?}""".toRegex()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A basic [TextProcessor] that replaces `${...}` expressions in text. The following expressions are recognised:
|
* A basic [TextProcessor] that replaces `${...}` expressions in text. The following expressions are recognised:
|
||||||
@ -16,30 +16,44 @@ public class WebPageTextProcessor(private val page: PageContext) : TextProcessor
|
|||||||
* * `pageMeta.get("...") -> [PageContext.pageMeta] get string method
|
* * `pageMeta.get("...") -> [PageContext.pageMeta] get string method
|
||||||
* Otherwise return unchanged string
|
* Otherwise return unchanged string
|
||||||
*/
|
*/
|
||||||
override fun process(text: CharSequence): String = text.replace(regex) { match ->
|
override fun process(text: CharSequence): String = text.replace(functionRegex) { match ->
|
||||||
when (match.groups[1]!!.value) {
|
when (match.groups["target"]?.value) {
|
||||||
"homeRef" -> page.homeRef
|
"homeRef" -> page.homeRef
|
||||||
"resolveRef" -> {
|
"resolveRef" -> {
|
||||||
val refString = match.groups[2]?.value ?: error("resolveRef requires a string (quoted) argument")
|
val refString = match.groups["name"]?.value ?: error("resolveRef requires a string (quoted) argument")
|
||||||
page.resolveRef(refString)
|
page.resolveRef(refString)
|
||||||
}
|
}
|
||||||
|
|
||||||
"resolvePageRef" -> {
|
"resolvePageRef" -> {
|
||||||
val refString = match.groups[2]?.value
|
val refString = match.groups["name"]?.value
|
||||||
?: error("resolvePageRef requires a string (quoted) argument")
|
?: error("resolvePageRef requires a string (quoted) argument")
|
||||||
page.localisedPageRef(refString.parseAsName())
|
page.localisedPageRef(refString.parseAsName())
|
||||||
}
|
}
|
||||||
|
|
||||||
"pageMeta.get" -> {
|
"pageMeta.get" -> {
|
||||||
val nameString = match.groups[2]?.value
|
val nameString = match.groups["name"]?.value
|
||||||
?: error("resolvePageRef requires a string (quoted) argument")
|
?: error("resolvePageRef requires a string (quoted) argument")
|
||||||
page.pageMeta[nameString.parseAsName()].string ?: "@null"
|
page.pageMeta[nameString.parseAsName()].string ?: "@null"
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> match.value
|
else -> match.value
|
||||||
}
|
}
|
||||||
|
}.replace(attributeRegex){ match->
|
||||||
|
val uri = URI(match.groups["uri"]!!.value)
|
||||||
|
val snarkUrl = when(uri.authority){
|
||||||
|
"homeRef"->page.homeRef
|
||||||
|
"ref" -> page.resolveRef(uri.path)
|
||||||
|
"page" -> page.localisedPageRef(uri.path.parseAsName())
|
||||||
|
"meta" -> page.pageMeta[uri.path.parseAsName()].string ?: "@null"
|
||||||
|
else -> match.value
|
||||||
|
}
|
||||||
|
"=\"$snarkUrl\""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public companion object {
|
||||||
|
internal val functionRegex = """\$\{(?<target>[\w.]*)(?:\((?:"|")(?<name>.*)(?:"|")\))?\}""".toRegex()
|
||||||
|
private val attributeRegex = """="(?<uri>snark://([^"]*))"""".toRegex()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -49,26 +63,28 @@ public class WebPageTextProcessor(private val page: PageContext) : TextProcessor
|
|||||||
public class Postprocessor<out R>(
|
public class Postprocessor<out R>(
|
||||||
public val page: PageContext,
|
public val page: PageContext,
|
||||||
private val consumer: TagConsumer<R>,
|
private val consumer: TagConsumer<R>,
|
||||||
private val processor: TextProcessor = WebPageTextProcessor(page),
|
private val textProcessor: TextProcessor,
|
||||||
) : TagConsumer<R> by consumer {
|
) : TagConsumer<R> by consumer {
|
||||||
|
|
||||||
override fun onTagAttributeChange(tag: Tag, attribute: String, value: String?) {
|
override fun onTagAttributeChange(tag: Tag, attribute: String, value: String?) {
|
||||||
if (tag is A && attribute == "href" && value != null) {
|
if (tag is A && attribute == "href" && value != null) {
|
||||||
consumer.onTagAttributeChange(tag, attribute, processor.process(value))
|
consumer.onTagAttributeChange(tag, attribute, textProcessor.process(value))
|
||||||
|
} else if (tag is IMG && attribute == "src" && value != null) {
|
||||||
|
consumer.onTagAttributeChange(tag, attribute, textProcessor.process(value))
|
||||||
} else {
|
} else {
|
||||||
consumer.onTagAttributeChange(tag, attribute, value)
|
consumer.onTagAttributeChange(tag, attribute, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTagContent(content: CharSequence) {
|
override fun onTagContent(content: CharSequence) {
|
||||||
consumer.onTagContent(processor.process(content))
|
consumer.onTagContent(textProcessor.process(content))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTagContentUnsafe(block: Unsafe.() -> Unit) {
|
override fun onTagContentUnsafe(block: Unsafe.() -> Unit) {
|
||||||
val proxy = object : Unsafe {
|
val proxy = object : Unsafe {
|
||||||
override fun String.unaryPlus() {
|
override fun String.unaryPlus() {
|
||||||
consumer.onTagContentUnsafe {
|
consumer.onTagContentUnsafe {
|
||||||
processor.process(this@unaryPlus).unaryPlus()
|
textProcessor.process(this@unaryPlus).unaryPlus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
package space.kscience.snark.html
|
||||||
|
|
||||||
|
import kotlinx.html.div
|
||||||
|
import kotlinx.html.unsafe
|
||||||
|
import kotlinx.io.Source
|
||||||
|
import kotlinx.io.readString
|
||||||
|
import space.kscience.snark.SnarkReader
|
||||||
|
|
||||||
|
|
||||||
|
public interface SnarkHtmlReader : SnarkReader<PageFragment>
|
||||||
|
|
||||||
|
public object HtmlReader : SnarkHtmlReader {
|
||||||
|
override val types: Set<String> = setOf("html")
|
||||||
|
|
||||||
|
override fun readFrom(source: String): PageFragment = PageFragment {
|
||||||
|
div {
|
||||||
|
unsafe { +source }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun readFrom(source: Source): PageFragment = readFrom(source.readString())
|
||||||
|
}
|
||||||
|
|
@ -1,49 +0,0 @@
|
|||||||
package space.kscience.snark.html
|
|
||||||
|
|
||||||
import kotlinx.html.div
|
|
||||||
import kotlinx.html.unsafe
|
|
||||||
import kotlinx.io.Source
|
|
||||||
import kotlinx.io.readString
|
|
||||||
import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor
|
|
||||||
import org.intellij.markdown.html.HtmlGenerator
|
|
||||||
import org.intellij.markdown.parser.MarkdownParser
|
|
||||||
import space.kscience.snark.SnarkReader
|
|
||||||
|
|
||||||
|
|
||||||
public interface SnarkHtmlReader: SnarkReader<PageFragment>
|
|
||||||
|
|
||||||
public object HtmlReader : SnarkHtmlReader {
|
|
||||||
override val types: Set<String> = setOf("html")
|
|
||||||
|
|
||||||
override fun readFrom(source: String): PageFragment = PageFragment {
|
|
||||||
div {
|
|
||||||
unsafe { +source }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun readFrom(source: Source): PageFragment = readFrom(source.readString())
|
|
||||||
}
|
|
||||||
|
|
||||||
public object MarkdownReader : SnarkHtmlReader {
|
|
||||||
override val types: Set<String> = setOf("text/markdown", "md", "markdown")
|
|
||||||
|
|
||||||
override fun readFrom(source: String): PageFragment = PageFragment {
|
|
||||||
val parsedTree = markdownParser.buildMarkdownTreeFromString(source)
|
|
||||||
val htmlString = HtmlGenerator(source, parsedTree, markdownFlavor).generateHtml()
|
|
||||||
|
|
||||||
div {
|
|
||||||
unsafe {
|
|
||||||
+htmlString
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val markdownFlavor = CommonMarkFlavourDescriptor()
|
|
||||||
private val markdownParser = MarkdownParser(markdownFlavor)
|
|
||||||
|
|
||||||
override fun readFrom(source: Source): PageFragment = readFrom(source.readString())
|
|
||||||
|
|
||||||
public val snarkReader: SnarkReader<PageFragment> = SnarkReader(this, "text/markdown")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user