【フロー】特定の区切り文字で文字列を分割する

Salesforceのフローにおいて、特定の区切り文字で文字列を分割する仕組みを考えてみます。

## 考える具体例
入力された文字列を入力された区切り文字で分割したコレクションを戻り値として返す自動起動フローを構築します。

:::note info
自動起動フローにしておくことで、外部のフローからいつでも呼び出すことができるようになります。
:::

## 文字列の分割アルゴリズム
文字列を分割するとき、一般的には以下のような手順で行います。

1. 文字列を左から読み進めて区切り文字を探す
   1. 区切り文字を発見した場合、区切り文字までの文字を切り出す
   2. 区切り文字を発見できなかった場合、文末までの文字列を切り出す
2. 文字列の文末まで進めたら終了する

上記の手順をフローのアルゴリズムに焼き直していきます。

## 実装方法
#### フローの種類を選択する
フローの種類は __「自動起動フロー（トリガーなし）」__ を選択します。

![Flow_SplitText_フロー種別選択.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3285411/7d8b0b00-56f0-7455-09d6-ccba3478463e.png)

#### 入力変数を定義する
入力変数として下記を準備します。

- __inputText__ ： 分割対象のテキストを入力するテキスト型の入力で使用可能な変数
- __inputSplitText__ ： 分割基準の区切り文字を入力するテキスト型の入力で使用可能な変数

![Flow_SplitText_入力変数_inputText.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3285411/41a4fe60-8835-3b1c-c0ef-f7f64319ae37.png)
![Flow_SplitText_入力変数_inputSplitText.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3285411/bf3839df-00dd-ffb1-4005-978db9b99c1f.png)

#### 出力変数を定義する
出力変数として下記を準備します。

- __splittedText__ : 分割されたテキストを出力するテキストコレクション型の出力で使用可能な変数

![Flow_SplitText_出力変数_splittedText.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3285411/34cb4fc5-5a76-9b46-552b-2e2ff6063f2a.png)

#### 数式リソースを定義する
数式リソースとして下記を準備します。

- __Formula_TextLength__ : inputTextの長さを算出する数値型の数式リソース

```
LEN( {!inputText} )
```

- __Formula_SplittedText__ : inputTextを区切り文字で分割した単一の文字列を算出するテキスト型の数式リソース

```
IF( CONTAINS( {!inputText} , {!inputSplitText} ) ,
  LEFT( {!inputText} , FIND( {!inputSplitText} , {!inputText} ) - 1 ) ,
  {!inputText}
)
```

- __Formula_RemainingText__ : 分割後の文字列を算出するテキスト型の数式リソース

```
IF( CONTAINS( {!inputText} , {!inputSplitText} ) ,
  RIGHT( {!inputText} , LEN( {!inputText} ) - LEN( {!Formula_SplittedText} ) - LEN( {!inputSplitText} ) ) ,
  ""
)
```

![Flow_SplitText_数式リソース_Formula_TextLength.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3285411/84ca8dd0-d334-aa1e-93ca-f8b4e0c93155.png)
![Flow_SplitText_数式リソース_Formula_SplittedText.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3285411/7591375c-a588-3ef0-c968-fd07c2368ee7.png)
![Flow_SplitText_数式リソース_Formula_RemainingText.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3285411/25ade18e-578c-23f6-b5c3-c8a12b3b9454.png)

#### 分割処理の必要性を判定する
テキストの長さを確認して分割処理が必要か判定します。
ここでは下記のような設定とします。

- API参照名 ： DecideSplitText
- 結果 - 1 ：
  - 表示ラベル ： 必要 
  - API参照名 ： DecideSplitText_Need
  - 条件 ： すべての条件に一致（AND）
    - {!Formula_TextLength} より大きい 0
- デフォルトの結果 ： 不要

![Flow_SplitText_決定_DecideSplitText.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3285411/3fc837ba-d31b-2eac-30be-bd227e77cebc.png)

#### コレクション変数へ追加する
分割処理が必要な場合は、コレクション変数へ分割したテキストを追加します。
ここでは下記のような設定とします。

- API参照名 ： Add_TextCollection
- 変数値を設定　：
  - {!splittedText} 追加 {!Formula_SplittedText}

![Flow_SplitText_割り当て_Add_TextCollection.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3285411/f61caabe-3969-f210-f9a3-7a7a8f9ecae6.png)

#### テキスト変数を上書きする
コレクション変数へ分割したテキストを追加した後に、テキスト変数の値を分割処理後のテキストで上書きします。
ここでは下記のような設定とします。

- API参照名 ： Assign_Text
- 変数値を設定　：
  - {!inputText}　次の文字列と一致する {!Formula_RemainingText}

![Flow_SplitText_割り当て_Assign_Text.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3285411/a8d5da60-d130-9bd4-a0ca-dcaaaa3fc911.png)

#### 要素を接続する
テキスト変数を上書きした後は再び分割処理の必要性を判定する必要があるため、決定要素へ要素の接続を行います。

#### フローの完成
完成したフローの形は下記のような形になります。

![Flow_SplitText_フロー完成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3285411/967aa342-4d5a-fcb9-c0b3-cec4f7bd6dc3.png)

#### フローの保存
フローを保存します。

![Flow_SplitText_フロー保存.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3285411/4a24c02c-2ca0-f0c1-a043-69ef6d2209ab.png)

## まとめ
特定の区切り文字で文字列を分割する操作は、ApexであればStringクラスのsplitメソッドで即座に行うことができますが、フローでは少し構築が必要になります。
フローの内容としては難しくはない内容ですが、初めから考えて構築するときに悩む点があるため、記録がてら残しています。

今回構築したフローはテキストを対象にしていますが、オブジェクトの複数選択リスト項目や画面フローの複数選択リストコンポーネントの結果が、``;``を区切り文字としたテキストとなっている点から、複数選択リストで選択された選択値のコレクションを取得する場合でも使用できるかと思います。

## おまけ
作成したフローのメタデータです。
```xml
<?xml version="1.0" encoding="UTF-8"?>
<Flow xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>61.0</apiVersion>
    <assignments>
        <name>Add_TextCollection</name>
        <label>コレクション変数へ追加する</label>
        <locationX>50</locationX>
        <locationY>242</locationY>
        <assignmentItems>
            <assignToReference>splittedText</assignToReference>
            <operator>Add</operator>
            <value>
                <elementReference>Formula_SplittedText</elementReference>
            </value>
        </assignmentItems>
        <connector>
            <targetReference>Assign_Text</targetReference>
        </connector>
    </assignments>
    <assignments>
        <name>Assign_Text</name>
        <label>テキスト変数を上書きする</label>
        <locationX>50</locationX>
        <locationY>350</locationY>
        <assignmentItems>
            <assignToReference>inputText</assignToReference>
            <operator>Assign</operator>
            <value>
                <elementReference>Formula_RemainingText</elementReference>
            </value>
        </assignmentItems>
        <connector>
            <isGoTo>true</isGoTo>
            <targetReference>DecideSplitText</targetReference>
        </connector>
    </assignments>
    <decisions>
        <name>DecideSplitText</name>
        <label>分割処理の必要性を判定する</label>
        <locationX>182</locationX>
        <locationY>134</locationY>
        <defaultConnectorLabel>不要</defaultConnectorLabel>
        <rules>
            <name>DecideSplitText_Need</name>
            <conditionLogic>and</conditionLogic>
            <conditions>
                <leftValueReference>Formula_TextLength</leftValueReference>
                <operator>GreaterThan</operator>
                <rightValue>
                    <numberValue>0.0</numberValue>
                </rightValue>
            </conditions>
            <connector>
                <targetReference>Add_TextCollection</targetReference>
            </connector>
            <label>必要</label>
        </rules>
    </decisions>
    <environments>Default</environments>
    <formulas>
        <name>Formula_RemainingText</name>
        <dataType>String</dataType>
        <expression>IF( CONTAINS( {!inputText} , {!inputSplitText} ) ,
  RIGHT( {!inputText} , LEN( {!inputText} ) - LEN( {!Formula_SplittedText} ) - LEN( {!inputSplitText} ) ) ,
  &quot;&quot;
)</expression>
    </formulas>
    <formulas>
        <name>Formula_SplittedText</name>
        <dataType>String</dataType>
        <expression>IF( CONTAINS( {!inputText} , {!inputSplitText} ) ,
  LEFT( {!inputText} , FIND( {!inputSplitText} , {!inputText} ) - 1 ) ,
  {!inputText}
)</expression>
    </formulas>
    <formulas>
        <name>Formula_TextLength</name>
        <dataType>Number</dataType>
        <expression>LEN( {!inputText} )</expression>
        <scale>0</scale>
    </formulas>
    <interviewLabel>文字列分割 {!$Flow.CurrentDateTime}</interviewLabel>
    <label>文字列分割</label>
    <processMetadataValues>
        <name>BuilderType</name>
        <value>
            <stringValue>LightningFlowBuilder</stringValue>
        </value>
    </processMetadataValues>
    <processMetadataValues>
        <name>CanvasMode</name>
        <value>
            <stringValue>AUTO_LAYOUT_CANVAS</stringValue>
        </value>
    </processMetadataValues>
    <processMetadataValues>
        <name>OriginBuilderType</name>
        <value>
            <stringValue>LightningFlowBuilder</stringValue>
        </value>
    </processMetadataValues>
    <processType>AutoLaunchedFlow</processType>
    <start>
        <locationX>56</locationX>
        <locationY>0</locationY>
        <connector>
            <targetReference>DecideSplitText</targetReference>
        </connector>
    </start>
    <status>Active</status>
    <variables>
        <name>inputSplitText</name>
        <dataType>String</dataType>
        <isCollection>false</isCollection>
        <isInput>true</isInput>
        <isOutput>false</isOutput>
    </variables>
    <variables>
        <name>inputText</name>
        <dataType>String</dataType>
        <isCollection>false</isCollection>
        <isInput>true</isInput>
        <isOutput>false</isOutput>
    </variables>
    <variables>
        <name>splittedText</name>
        <dataType>String</dataType>
        <isCollection>true</isCollection>
        <isInput>false</isInput>
        <isOutput>true</isOutput>
    </variables>
</Flow>
```