0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GitBucket Markdown Enhanced Plugin で KaTeX の記法を Markdown Preview Enhanced に合わせました

Posted at

概要

GitBucket用のプラグイン GitBucket Markdown Enhanced Pluginを開発しています。

GitBucket Markdown Enhanced Plugin は、GitBucket 標準のマークダウンレンダリングエンジンを置き換えるプラグインです。

目標は、Visual Studio CodeMarkdown Preview Enhanced 向けの markdown ファイルを軽易に Web で共有できる環境です。

これまで数式の描画は、flexmark-javaGitLab Flavoured Markdown という拡張機能により、実現していました。

ところが、この拡張機能の記法は、Markdown Preview Enhanced が採用している記法と異なっていました。今回は、これまでの記法に加え、Markdown Preview Enhanced が採用している記法に対応した際の記録です。

前回の記事

GitLab Flavoured Markdown と Markdown Preview Enhanced での数式記法の違い

GitLab Flavoured Markdown の記法

  • $``$ ($``$ の間に KaTeX の記法で書く)
  • ```math で始まるコードブロック

Markdown Preview Enhanced の記法

  • インライン記法
    • $...$
    • \(...\)
  • ブロック記法
    • $$...$$
    • \[...\]
    • ```math で始まるコードブロック

```math で始まるコードブロックは GitLab Flavoured Markdown でも対応していますが、その他は対応してません。

Markdown Preview Enhanced の記法に対応したコミット

KaTeX syntax adapted to MPE · yasumichi/gitbucket-markdown-enhanced@623d786

別記事に書いていますが、このコードには問題があったため、最終的なコードとは異なっていることをご了承ください。

AST 用ノードの作成

src/main/scala/io/github/yasumichi/gme/InlineKaTeX.scala で AST1 用のノードを作成しました。

src/main/scala/io/github/yasumichi/gme/InlineKaTeX.scala
package io.github.yasumichi.gme

import com.vladsch.flexmark.util.ast.Node
import com.vladsch.flexmark.util.sequence.BasedSequence

/**
  * InlineKatex class
  *
  * AST node that holds the inline KaTeX syntax of flexmark-java.
  * 
  * `$...$` is represented as InlineKatex node.
  * An instance is created by the InlineKatexInlineParserExtension class.
  *
  * @param text
  * @param source
  */
class InlineKatex(val text: BasedSequence, val source: BasedSequence) extends Node {
  override def getSegments: Array[BasedSequence] = Array(source)
}

インラインパーサーの作成

次に上で作成したノードに格納する部分を担うインラインパーサーを作成しました。

src/main/scala/io/github/yasumichi/gme/InlineKatexInlineParserExtension.scala

src/main/scala/io/github/yasumichi/gme/InlineKatexInlineParserExtension.scala
package io.github.yasumichi.gme

import com.vladsch.flexmark.util.ast.Node
import com.vladsch.flexmark.util.sequence.BasedSequence
import com.vladsch.flexmark.parser.{
  InlineParser,
  InlineParserExtension,
  InlineParserExtensionFactory,
  LightInlineParser
}

import org.slf4j.LoggerFactory

/**
  * InlineKatexInlineParserExtension class
  * 
  * Inline parser extension that parses the inline KaTeX syntax.
  * 
  * It recognizes the following patterns:
  * - `\[...\]` 
  * - `$$...$$`
  * - `$...$`
  * - `\(...\)`
  */
class InlineKatexInlineParserExtension extends InlineParserExtension {
  private val logger = LoggerFactory.getLogger(classOf[InlineKatexInlineParserExtension])

  override def finalizeDocument(inlineParser: InlineParser): Unit = {}

  override def finalizeBlock(inlineParser: InlineParser): Unit = {}

  override def parse(inlineParser: LightInlineParser): Boolean = {
   val patterns = List("""\\\[(.+?)\\\]""", """\$\$(.+?)\$\$""", """\$(.+?)\$""", """\\\((.+?)\\\)""")
    logger.debug("Input: " + inlineParser.getInput().toString())

    for (patternText <- patterns) {
      logger.debug(s"Trying pattern: $patternText")
      val matches = inlineParser.matchWithGroups(java.util.regex.Pattern.compile(patternText))
      if (matches != null) {
        logger.debug(s"Matched pattern: $patternText with content: ${matches(1)}")
        inlineParser.flushTextNode()
        val katexText = matches(1)
        inlineParser.getBlock.appendChild(new InlineKatex(katexText, matches(0)))
        return true
      }
    }
    false
  }
}

object InlineKatexInlineParserExtension {
  class Factory() extends InlineParserExtensionFactory {
    override def getCharacters: CharSequence = "$\\"
    override def apply(inlineParser: LightInlineParser): InlineParserExtension = new InlineKatexInlineParserExtension()
    override def getAfterDependents: java.util.Set[Class[_]] = null
    override def getBeforeDependents: java.util.Set[Class[_]] = null
    override def affectsGlobalScope(): Boolean = false
  }
}

インラインパーサーを拡張機能に組み込む

上記で作成したインラインパーサーを拡張機能に組み込みます。関連部分のみ抜粋します。

src/main/scala/io/github/yasumichi/gme/MarkdownEnhancedExtention.scala

src/main/scala/io/github/yasumichi/gme/MarkdownEnhancedExtention.scala
class MarkdownEnhancedExtention extends ParserExtension with HtmlRendererExtension {

  /**
  * Extend the parser with custom inline parser extension
  *
  * @param parserBuilder The parser builder to extend
  */
  override def extend(parserBuilder: Parser.Builder): Unit = {
    parserBuilder.customInlineParserExtensionFactory(new MarkInlineParserExtension.Factory())
    parserBuilder.customInlineParserExtensionFactory(new InlineUriInlineParserExtension.Factory())
    parserBuilder.customInlineParserExtensionFactory(new InlineKatexInlineParserExtension.Factory())  // ここを追加
  }

InlineKatex ノードを HTML に変換する部分

InlineKatex ノードを HTML に変換する部分を実装します。

src/main/scala/io/github/yasumichi/gme/MarkdownEnhancedNodeRenderer.scala

まず、既存の MarkdownEnhancedNodeRenderer で InlineKatex ノードを処理する意思表示と処理するメソッドを登録します。

src/main/scala/io/github/yasumichi/gme/MarkdownEnhancedNodeRenderer.scala
  override def getNodeRenderingHandlers(): util.Set[NodeRenderingHandler[_ <: Object]] = {
(中略)
    set.add(
      new NodeRenderingHandler[InlineKatex](
        classOf[InlineKatex],
        this.renderInlineKatex
      )
    )

実際の処理を行う renderInlineKatex メソッドを実装します。

src/main/scala/io/github/yasumichi/gme/MarkdownEnhancedNodeRenderer.scala
  private def renderInlineKatex(
      node: InlineKatex,
      context: NodeRendererContext,
      html: HtmlWriter
  ): Unit = {
    if (node.source.startsWith("$$") || node.source.startsWith("\\[")) {
      // Display math
      html
        .withAttr()
        .attr("class", "katex")
        .tag("div")
      html.text(node.text.toString())
      html.tag("/div")
    } else {
      // Inline math
      html
        .withAttr()
        .attr("class", "katex")
        .tag("span")
      html.text(node.text.toString())
      html.tag("/span")
    }
  }

参考リンク

  1. AST : Abstract Syntax Tree 、日本語では抽象構文木と訳される。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?