LoginSignup
8

More than 1 year has passed since last update.

[UE4] RichTextBlock で独自タグを定義する

Last updated at Posted at 2021-01-11

RichTextBlock で遊んでみよう第三弾!
といいつつ、今回のメインは正規表現の話だったりします。
27b6c927cec57ee0fcef60e7ae0aa8bc.png
例えばゲーム内通貨名。世界観によって様々で、開発の途中で変わることもしばしばです。

この世界のお金は"<#Word:Money>"になります。

その度にテキストを全体置換するよりも、最初からメタデータで変数化できてるとよいですね。
ae6d5644761ad687e9d5214bd11a6001.png
例えばデコレータのタグ入力。

<ruby label="わがはい">吾輩</>は<ruby label="ねこ" style="Important">猫</>である。

なんだか冗長に見えるこれより、

|吾輩"わがはい"は|猫"ねこ"<Important>である。

簡略的に分かりやすく入力したいですよね。今回はそんなお話です。

RichTextBlock テキストの置換処理

前々回の記事で拡張した、FMyDefaultRichTextMarkupParser に処理を加えます。
構文解析前の文字列を正規表現で直接修正します。

FMyDefaultRichTextMarkupParser.cpp
void FMyDefaultRichTextMarkupParser::Process(
    TArray<FTextLineParseResults>& Results, const FString& Input, FString& Output )
{
// ============================ // ここから
    FString ProcessInput( Input );

    // 単語テキスト用タグの置換処理
    ProcessReplaceString( ProcessInput );

    // ルビ用タグの置換処理
    ProcessReplaceRuby( ProcessInput );
// ============================ // ここまで
    // ※以降は Input の参照を ProcessInput に変更する
    // エンジン側実装の構文解析処理
    TArray<FTextRange> LineRanges;
    ... // 省略
}

正規表現を思い出す

正規表現なんて年に1度使えばいいくらいの私なので、使うたびにググっていたのですが、今回とてもよい記事に出会えたのでメモしておきます。
正規表現を可視化してまとめたチートシート

正規表現の可視化するために利用しているサービスはregexper.comです。
各正規表現のチェックにはrubular.comなどを利用して確認する。

今まではプログラム実行して確かめていたのでw
イテレーションが非常に早くて感動しています。正規表現チョット楽しい!になった。

単語テキスト用タグの置換処理

この世界のお金は"<#Word:Money>"になります。

構文ルールは <#{ReplaceKey}:{ReplaceValue}> とします。
image (3).png
Key/Value でデータテーブルなどから単語を参照して置換する感じです。

FMyDefaultRichTextMarkupParser.cpp
void FMyDefaultRichTextMarkupParser::ProcessReplaceString(
    FString& InOutString ) const
{
    FRegexPattern RegexPattern( TEXT( "<#([\\w]+):([\\w]+)>" ) );
    FRegexMatcher RegexMatcher( RegexPattern, InOutString );

    struct FReplaceKeyValuePair
    {
        FString Key;
        FString Value;
    };

    TMap<FTextRange, FReplaceKeyValuePair> ReplaceMap;
    while ( RegexMatcher.FindNext() )
    {
        FReplaceKeyValuePair KVP;
        KVP.Key = RegexMatcher.GetCaptureGroup( 1 );
        KVP.Value = RegexMatcher.GetCaptureGroup( 2 );

        FTextRange ElementRange(
            RegexMatcher.GetMatchBeginning(), RegexMatcher.GetMatchEnding() );
        ReplaceMap.Emplace( ElementRange, KVP );
    }

    TArray<FTextRange> Keys;
    ReplaceMap.GetKeys( Keys );

    // HITしたパターンを逆順にしてから
    Algo::Reverse( Keys );

    for ( const FTextRange& Key : Keys )
    {
        const FReplaceKeyValuePair& KVP = ReplaceMap[Key];

        FString ReplacedString = ReplaceString( KVP.Key, KVP.Value );
        if ( !ReplacedString.IsEmpty() )
        {
            // テキスト後方から置換していく
            InOutString.RemoveAt( Key.BeginIndex, Key.EndIndex - Key.BeginIndex );
            InOutString.InsertAt( Key.BeginIndex, ReplacedString );
        }
    }
}

FString FMyDefaultRichTextMarkupParser::ReplaceString(
    const FString& ReplaceTag, const FString& ReplaceValue ) const
{
    if ( ReplaceTag == TEXT( "Word" ) )
    {
        return // DataTableなどからReplaceValueを参照して取得する
    }
    return TEXT( "" );
}

ルビ用タグの置換処理

|吾輩"わがはい"は|猫"ねこ"<Important>である。

構文ルールは |{ContentString}"{RubyString}"<{TextStyleName}> とします。
小説家になろうのルビ仕様を参考にしました。
TextStyleName はオプションです。
image (2).png
0e373075b190c2fcaf7fe9bfd82000fb.png
※上記ページでは "|" 文字が使えなかったので "!" を使っています。

FMyDefaultRichTextMarkupParser.cpp
void FMyDefaultRichTextMarkupParser::ProcessReplaceRuby( FString& InOutString ) const
{
    FRegexPattern RegexPattern( TEXT( "\\|([\\w]+)\"([\\w]+)\"(<([\\w]+)>)?" ) );
    FRegexMatcher RegexMatcher( RegexPattern, InOutString );

    struct FLabelWithRuby
    {
        FString Ruby;
        FString Label;
        FString Style;
    };

    TMap<FTextRange, FLabelWithRuby> ReplaceMap;
    while ( RegexMatcher.FindNext() )
    {
        FLabelWithRuby LabelWithRuby;
        LabelWithRuby.Label = RegexMatcher.GetCaptureGroup( 1 );
        LabelWithRuby.Ruby = RegexMatcher.GetCaptureGroup( 2 );
        LabelWithRuby.Style = RegexMatcher.GetCaptureGroup( 4 );

        FTextRange ElementRange(
            RegexMatcher.GetMatchBeginning(), RegexMatcher.GetMatchEnding() );
        ReplaceMap.Emplace( ElementRange, LabelWithRuby );
    }

    TArray<FTextRange> Keys;
    ReplaceMap.GetKeys( Keys );

    Algo::Reverse( Keys );

    for ( const FTextRange& Key : Keys )
    {
        InOutString.RemoveAt( Key.BeginIndex, Key.EndIndex - Key.BeginIndex );

        const FLabelWithRuby& LabelWithRuby = ReplaceMap[Key];

        // Attributeを配列に格納して、後でJoinする
        TArray<FString> AttributeStrings =
            { FString::Printf( TEXT( "ruby=\"%s\"" ), *LabelWithRuby.Ruby ) };

        if ( !LabelWithRuby.Style.IsEmpty() )
        {
            AttributeStrings.Emplace(
                FString::Printf( TEXT( "style=\"%s\"" ), *LabelWithRuby.Style ) );
        }

        InOutString.InsertAt(
            Key.BeginIndex, FString::Format( TEXT( "<ruby {0}>{1}</>" ), {
                FString::Join( AttributeStrings, TEXT( "\u0020" ) ),
                LabelWithRuby.Label,
            } ) );
    }
}

RichTextBlock 完全に理解した

download.jpg
だいたいやりたいことはやったかな?
構文ネストだけが心残りです。ぐぬぬ・・・。

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
What you can do with signing up
8