flutter_markdownでカスタムのインラインタグを実装する方法

はじめに

flutter_markdownでカスタムのブロックタグを実装する方法

前回はflutter_markdownでカスタムのブロックタグを実装する方法を紹介しました。
今回はインラインタグの実装方法について紹介します。

カスタムのブロックタグを実装した箇所と被っている部分は省略します。
上記のブログをご覧ください。

今回作るもの

テキスト <green_inline content="greenテキスト" /> てきすと

上記のような <green_inline content="..."> があった場合、 content の文字色を緑にするようなものを作っていきます。

実装方法

MarkdownBodyの説明

カスタムのブロックタグの時と同様に MarkdownBody を使って表示していきます。
ブロックタグの際はMarkdownデータをパースする際に使用するカスタムブロックタグの指定は blockSyntaxes で行なっていましたが、インラインブロックの際は inlineSyntaxes に指定します。 Widgetの表示に関しては ブロックタグのサイト同様に builders で指定します。

return fmd.MarkdownBody(
  // 表示するMarkdownの文字列
  data: content,
  inlineSyntaxes: <InlineSyntax>[
    // ...
  ],
  builders: <String, MarkdownElementBuilder>{
    // ...
  },
);

では InlineSyntax を実装していきます。

InlineSyntaxの実装

以下が InlineSyntax の実装例です。

import 'package:markdown/markdown.dart' as md;

class GreenInlineMarkdownSyntax extends md.InlineSyntax {
  GreenInlineMarkdownSyntax() : super(r'<green_inline\s+content="([^"]+)".*?\/?>');

  @override
  bool onMatch(md.InlineParser parser, Match match) {
    final content = match.group(1) ?? '';

    // カスタムノードを作成
    final element = md.Element('green_inline', []);
    element.attributes['content'] = content;

    parser.addNode(element);

    return true;
  }
}

1. InlineSyntaxを継承したクラスの作成

カスタムのタグを追加するために、InlineSyntax を継承したクラスを作成します。

2. パターンの定義

コンストラクタの super<green_inline content="..." /> を識別するための正規表現を定義します。

  GreenInlineMarkdownSyntax() : super(r'<green_inline\s+content="([^"]+)".*?\/?>');

正規表現の説明

3. onMatchメソッドの実装

onMatch で一致するタグから値を取得し、カスタムノード(Element)を作成します:

  @override
  bool onMatch(md.InlineParser parser, Match match) {
    final content = match.group(1) ?? '';

    // カスタムノードを作成
    final element = md.Element('green_inline', []);
    element.attributes['content'] = content;

    parser.addNode(element);

    return true;
  }

これで、inlineSyntaxes 側の実装は完了です。

MarkdownElementBuilderの実装

次に、 MarkdownBodybuilders に設定する MarkdownElementBuilder の実装について解説します。

class GreenInlineMarkdownBuilder extends fmd.MarkdownElementBuilder {
  @override
  bool isBlockElement() => false;

  @override
  Widget? visitElementAfterWithContext(
    BuildContext context,
    md.Element element,
    TextStyle? preferredStyle,
    TextStyle? parentStyle,
  ) {
    return Text.rich(
      TextSpan(
        text: ' ${element.attributes['content']} ',
        style: parentStyle?.copyWith(color: Colors.green),
      ),
    );
  }
}

1. MarkdownElementBuilderを継承したクラスを作成

カスタム要素をレンダリングするために、 MarkdownElementBuilder を継承したクラスを作成します。

2. isBlockElementのオーバーライド

デフォルトで isBlockElementfalse なので、わざわざ指定する必要はありませんが、説明のため追加しています。ブロックタグの場合はここを true にします。

@override
bool isBlockElement() => false;

3. visitElementAfterWithContextの実装

カスタムタグがレンダリングされる際に表示するWidgetを定義します。
テキストは flutter_markdown の中を見てみると TextSpan で表示しているようなので、 Text.rich を使用して、 TextSpan を定義します。
またテキストのスタイルは引数の parentStyle を使って指定しています。これにより親要素のTextStyleを利用して表示することができます。

  @override
  Widget? visitElementAfterWithContext(
    BuildContext context,
    md.Element element,
    TextStyle? preferredStyle,
    TextStyle? parentStyle,
  ) {
    return Text.rich(
      TextSpan(
        text: '${element.attributes['content']}',
        style: parentStyle?.copyWith(color: Colors.green),
      ),
    );
  }

MarkdownBodyの実装

これまでに作成した GreenInlineSyntaxGreenInlineBuilderMarkdownBody にセットします。

return fmd.MarkdownBody(
  inlineSyntaxes: const [
    GreenInlineSyntax(),
  ],
  builders: {
    'green_inline': GreenInlineBuilder(),
  },
);

これで実装完了です!!

最後に

カスタムのインラインタグの実装方法について解説しました。カスタムのブロックタグ、インラインタグを定義することで、あらゆる表現ができると思います。

まだまだflutter_markdownの知見があるので、また紹介しようと思います。