LoginSignup
7
8

More than 3 years have 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
だいたいやりたいことはやったかな?
構文ネストだけが心残りです。ぐぬぬ・・・。

7
8
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
7
8