TRADACOMS EDI コネクタ

Premium

TRADACOMS EDI コネクタは、リストとマップを使用して TRADACOMS トランスミッションと DataWeave 互換表現との間で相互に変換を行います。

サポートされる TRADACOMS ファイルとバージョンは、ACKMNT:4、AVLDET:4、CORDER:6、CRAINF:3、CREDIT:9、CUSINF:8、DELIVR:9、DLCDET:5、DRAINF:3、EXCINF:3、GENRAL:3、INVOIC:9、LPRDET:2、ORDERS:9、PAYINF:3、PICKER:4、PPRDET:2、PRIINF:8、PROINF:8、SADDET:3、SNPSTS:3、SRMINF:9、UCNDET:3、UPLIFT:4、UTLBIL:3 です。

前提条件

このドキュメントでは、読者が TRADACOMS、Mule、Anypoint コネクタ、Anypoint Studio、Mule フロー、そして Mule グローバル要素に精通していることを前提としています。

互換性の詳細は、リリースノートを参照してください。リリースノートへのリンクは、このドキュメントの「関連情報」セクションに含まれます。

TRADACOMS EDI コネクタを本番環境で使用するには、Anypoint B2B 用の MuleSoft ライセンスを購入しておく必要があります。

このコネクタの新機能

TRADACOMS EDI コネクタ 2.x では、TRADACOMS EDI コネクタ 1.x で実装されていた設定と処理にいくつかの変更が加えられています。

  • TRADACOMS EDI コネクタ 1.x では、標準の単一バージョンに準拠したメッセージのみをサポートします。Mule 4 TRADACOMS EDI コネクタ 2.x は、標準の複数バージョンのメッセージをサポートします。メッセージヘッダー (UNH セグメント) データ内のバージョン情報を使用して標準バージョンを識別し、メッセージ識別子により特定のスキーマが決まります。

  • 読み取りおよび書き込み操作で ID パラメータが使用されなくなりました。書き込み操作に渡す実際のメッセージデータ内ですべてのパラメータ値を設定できます。

  • TRADACOMS EDI コネクタ 2.x では、「デフォルト」のスキーマ設定のサポートが追加されています。設定でスキーマが参照されていない場合、メッセージごとに標準バージョンのスキーマがクラスパスから検索され、読み取りと書き込みで使用されます。この場合、デザインの段階では実際のメッセージは未知となるため、メッセージのデータ構造のメタデータはありません。

Anypoint Studio 7 でこのコネクタをインストールする方法

  1. Anypoint Studio で、Studio タスクバーの Exchange アイコンをクリックします。

  2. Anypoint Exchange で [Login (ログイン)] をクリックします。

  3. コネクタを検索して [Install (インストール)] をクリックします。

  4. 画面の指示に従ってコネクタをインストールします。

Studio の更新がある場合、右下隅にメッセージが表示されます。メッセージをクリックすると、更新をインストールできます。

TRADACOMS EDI コネクタ 2.0.0 を使用するようにデフォルトの Anypoint 設定を変更する必要があります。

スキーマの作成方法

実装規定が標準とは異なる場合、スキーマを作成し、実装に従ってメッセージを記述することができます。定義されているすべての TRADACOM ファイル用のスキーマの標準バージョンは、ディストリビューションに含まれています。

EDI スキーマ言語について

TRADACOMS EDI モジュールは、ESL (EDI Schema Language) と呼ばれる YAML 形式を使用して EDI スキーマを表現します。ESL は、構造 (TRADACOMS の用語ではメッセージ)、グループ、セグメント、複合、および要素について EDI メッセージの構造を定義しています。

TRADACOMS に関する ESL 特有の使い方には、特に TRADACOMS における「ファイル」と「メッセージ」の概念に関して他の EDI 形式とは相違点がいくつかあります。TRADACOMS が定義する各ファイル形式は、特定の用途向けのデータを表現しています。ファイル形式は、いくつかのメッセージコンポーネントに分割され、特定のメッセージコンポーネント 1 つは反復できます。各メッセージコンポーネントはさらにいくつかのセグメントとセグメントグループに分割され、それらの一部も反復できます。

ESL では、各 TRADACOMS ファイル形式は、特定のスキーマ定義で表現されます。用意されている ESL スキーマは、ファイルの汎用名を使用します。この汎用名は、反復する詳細メッセージの名前と同じです。したがって、たとえば、Prince Information File 構造は PRIINF.esl スキーマで定義されます。ファイルスキーマ内では、各コンポーネントメッセージ種別が構造として定義されます。ファイル内でのコンポーネントメッセージの順序は、1 桁の文字列である class 値によって決定されます。値 '1' はヘッダーメッセージ、値 '2' は反復詳細メッセージ、それ以上のすべての値は、ファイル内の後続メッセージを表します。

オーバーレイスキーマを定義することで、TRADACOMS スキーマ定義をデータに合わせてカスタマイズできます。オーバーレイスキーマとは特殊な形式の ESL であり、TRADACOMS INVOIC などのベーススキーマを、独自の規則に従って変更することができます。標準で定義されている構造を使用する場合は、オーバーレイスキーマは必要ありません。

また、独自のスキーマをゼロから定義したり、TRADACOMS のベーススキーマをコピーして、データに合わせて編集したりすることもできます。詳細は、EDI Schema Language Referenceを参照してください。

YAML では、リストとキー-値ペアのセットの組み合わせが使用されます。必須項目が存在する場合、値の順序は重要ではありません。数字で構成される可能性があっても文字列として解釈するように意図される値は、引用符 (単一引用符または二重引用符) で囲みます (そうしないと、YAML パーサーは値を数値として処理するため)。インデントを使用して、リストのネストを示します。

読みやすくすることを目的として、ここで示している ESL 構造では、同じ定義の一部であるリストの前に、すべての簡単なキー-値ペアを定義しています。

オーバーレイスキーマを使用して実装規定を定義する方法

実装規定に従ってスキーマを指定するには、次の手順に従います。

  1. カスタマイズするベーススキーマ (例: TRADACOMS INVOIC) をインポートするオーバーレイスキーマを作成します。

  2. セグメントの使用方法、位置、グループ、および数に関する全体的な構造をカスタマイズします。

  3. 使用方法や数など、セグメントをカスタマイズします。

オーバーレイスキーマの構造はEDI Schema Language Referenceとよく似ていますが、オーバーレイスキーマでは、スキーマ構造のすべての詳細が提供される代わりに、変更のみがリストされます。オーバーレイスキーマでは、実装規定をどのように使用するかを特定の取引パートナーと共に特定し、標準を拡張およびカスタマイズします。

例として、基本的な TRADACOMS INVOIC のファイル定義を変更するオーバーレイスキーマのサンプルを示します。この例では、バージョン 6 の REBILL メッセージをファイルの他のすべての既存メッセージの後に追加しています (INVOIC ファイルには通常 4 つのメッセージコンポーネントがあるため、クラス値は '5' です)。

form: TRADACOMS
version: 'INVOIC9'
imports: [ '/tradacoms/INVOIC.esl' ]
structures:
- id: 'REBILL'
  name: 'REBILL:6'
  class: '5'
  data:
  - { idRef: 'MHD', usage: M }
  - { idRef: 'RBL', usage: M }
  - { idRef: 'MTR', usage: M }
segments:
- id: 'RBL'
  name: 'REBILLING DETAILS'
  values:
  - { id: 'RBLA', name: 'Rebill From Field', usage: M, type: char, minLength: 1, maxLength: 14 }
  - { id: 'RBLB', name: 'Rebill To Field', usage: M, type: char, minLength: 1, maxLength: 14 }
  - { idRef: 'PAI', position: '0040', usage: U }

構造オーバーレイ

構造オーバーレイは、TRADACOMS メッセージのベーススキーマ定義への変更を詳細に定義します。ほとんどの場合、この変更では、ベース定義内のセグメントまたはグループを未使用としてマークするという形式が使用されますが、使用方法または反復数の変更が許可されます。

構造オーバーレイの例を示します。

structures:
- idRef: 'INVFIL'
  data:
  - { idRef: 'FDT', position: '07', usage: M }
  - { idRef: 'ACD', position: '08', usage: M }

この例の変更は、FDT セグメント (位置 7) と ACD セグメント (位置 8) が各 INVFIL メッセージで必要であることを指定しています (用途: M、必須の意)。このオーバーレイにより、FDT セグメントまたは ACD セグメントがメッセージに含まれていない場合にエラーが報告されます。

構造レベルのキー-値ペアを次に示します。

Key (キー) 説明

idRef

変更するメッセージの ID。

class

ファイル内のメッセージの位置 (省略可能)。

name (名前)

メッセージの名前とバージョン (省略可能)。

data

構造内でのセグメントとグループへの変更のリスト (省略可能。それぞれ該当セクションに変更がある場合にのみ使用されます)。

構造データコンポーネントリストの各項目は、セグメント参照またはグループ定義です。ここでは、どちらもコンパクトな YAML 構文を使用しており、各参照の値はカンマで区切られたキーと値のペアとして、中括弧内に指定されています。値は次のとおりです。

値は次のとおりです。

Key (キー) 説明

idRef

参照されるセグメント ID。これは省略可能で、指定された場合は検証されますが、それ以外の場合は無視されます。position (位置) の値を使用して、セクション内のセグメントを一意に識別します。

position (位置)

メッセージセクション内のセグメントの位置。

usage (使用方法)

用途コード。M は必須、C は条件的、U は未使用。

count (数)

最大反復数の値。数値、または任意の反復数を意味する特別な値「>1」を使用できます。count (数) の値は省略可能で、値を指定しない場合、ベース定義の値が使用されます。

グループ定義内の値を次に示します。

Key (キー) 説明

groupIdRef

参照されるグループ ID。このキーは省略可能で、指定された場合は検証されますが、それ以外の場合は無視されます。position (位置) の値を使用して、セクション内のグループを一意に識別します。

position (位置)

メッセージセクション内のセグメントの位置。

usage (使用方法)

用途コード。M は必須、C は条件的、U は未使用。

count (数)

最大反復数の値。数値、または任意の反復数を意味する特別な値 (「>1」) を使用できます。count (数) の値は省略可能で、値を指定しない場合、ベース定義の値が使用されます。

items (項目)

グループを構成するセグメント (およびネストされている可能性のあるグループ) のリスト。

セグメントオーバーレイ

セグメントオーバーレイも、ベーススキーマ定義への変更を詳細に定義します。ほとんどの場合、この変更では、ベース定義内の要素または複合を未使用としてマークするという形式が使用されますが、使用方法または反復数の変更が許可されます。セグメントオーバーレイの例を示します。

structures:
- idRef: 'INVFIL'
  data:
  - { idRef: 'FDT', position: '07' }
segments:
- idRef: 'FDT'
  values:
  - { position: 1, usage: M }
  - { position: 2, usage: M }

この例では、FDT セグメントのベース定義を変更して、セグメントで定義されている両方の値を必須項目としています (ベース定義ではどちらも省略可能です)。

セグメントの変更は、変更されたセグメントへの明確な参照が定義されたオーバーレイに含まれている構造にのみ影響します。そのため、メッセージ内でのセグメントの使い方について、用途や反復数など何も変更されない場合でも、FDT セグメント参照をスキーマの INVFIL メッセージ構造部分に含める必要があります。

セグメントオーバーレイ内のキー-値ペアを次に示します。

Key (キー) 説明

idRef

セグメント識別子。

trim (切り取り)

セグメント内の切り取り位置。つまり、この位置以降のすべての値は未使用としてマークされます (省略可能)。

values

個々の値の変更のリスト

values リストは、位置によってセグメント内の値を参照します。この参照の値を次に示します。

Key (キー) 説明

position (位置)

セグメント内の値の位置。

name (名前)

セグメント内の値の名前 (省略可能。指定しない場合はベース定義の値を使用)。

usage (使用方法)

用途コード。M は必須、C は条件的、U は未使用。

TRADACOMS スキーマの場所を決める方法

コネクタを使用するには、プロジェクト内のスキーマの場所を認識しておく必要があります。TRADACOMS 標準スキーマをカスタマイズしないで使用する場合は、スキーマの場所は /tradacoms/{file}.esl パターンに準拠するため、ORDERS ファイルのスキーマの場所は /tradacoms/ORDERS.esl になります。

1 つ以上のカスタムスキーマを使用している場合、それらを src/main/app 内のディレクトリの下に配置し、${app.home} を使用して場所を参照する必要があります。 たとえば、CREDIT スキーマ (オーバーレイまたはフル) を src/main/app/mypartner/CREDIT.esl 下に配置した場合、スキーマの場所は ${app.home}/mypartner/CREDIT.esl になります。

Mule Runtime は自動的に src/main/app を参照し、${app.home} 値を含む場所があるかどうかを確認します。

Anypoint Studio 7 で Mule プロジェクトを作成する方法

コネクタをインストールし、(必要に応じて) スキーマをカスタマイズしたら、コネクタの使用を開始できます。実装規定ごとに個別の設定を作成します。

  1. キャンバスの下部にある [Global Elements (グローバル要素)] タブをクリックし、[Create (作成)] をクリックします。

  2. [Choose Global Type (グローバル種別の選択)] で [TRADACOMS EDI: Configuration (TRADACOMS EDI: 設定)] を見つけて選択し、[OK] をクリックします。

  3. [OK] をクリックして、グローバルコネクタ設定を保存します。

  4. Studio の [Message Flow (メッセージフロー)] タブに戻ります。

以降のセクションで説明するように、[Global Element Properties (グローバル要素のプロパティ)] の各タブを設定します。

[General (一般)] タブについて

[General (一般)] タブのプロパティ

[General (一般)] タブでは、STX セグメントで使用する識別情報など、TRADACOMS メッセージを読み書きするための設定を行います。

  • 使用するメッセージ構造のスキーマ定義のリストを手動で作成するか、編集します。

  • パートナー送信者/受信者コード (STX FROM または UNTO コード)。

  • パートナー送信者/受信者名 (STX FROM または UNTO 名)。

  • Mule アプリケーション送信者/受信者コード (STX FROM または UNTO コード)。

  • Mule アプリケーション送信者/受信者名 (STX FROM または UNTO 名)。

これらの識別値はすべて省略可能です。これらの値を設定した場合、入力トランスミッションを読む際には値が検証され、出力トランスミッションを作成する際には、値が指定されていない場合のデフォルト値として使用されます。

[Parser (パーサー)] タブについて

[Parser (パーサー)] タブのプロパティ

必要に応じて受信メッセージのパーサー検証を制御する以下のオプションを設定できます。

  • 受信値の最小長と最大長を適用します。

  • メッセージ内の不明なセグメントを許可します。

  • メッセージ内で「未使用」としてマークされたセグメントを許可します

  • メッセージ内のセグメント順序を適用します。

[Writer (ライター)] タブについて

[Writer (ライター)] タブのプロパティ

メッセージを書き込むための設定

  • トランスミッションを作成する際に使用される Default Sender’s Transmission Reference (デフォルトの送信者のトランスミッション参照)。

  • トランスミッションを作成する際に使用される Default Sender’s Transmission Reference (デフォルトの送信者のトランスミッション参照)。

  • トランスミッションを作成する際に使用される Default Application Reference (デフォルトのアプリケーション参照)。

  • トランスミッションを作成する際に使用される Default Application Reference (デフォルトのアプリケーション参照)。

例: TRADACOMS Studio

次のフローをプロジェクトの XML に読み込むことができます。

マッピングフローの図
<?xml version="1.0" encoding="UTF-8"?>

<mule xmlns:ee="http://www.mulesoft.org/schema/mule/ee/core"
xmlns:tradacoms="http://www.mulesoft.org/schema/mule/tradacoms"
	xmlns:http="http://www.mulesoft.org/schema/mule/http"
	xmlns="http://www.mulesoft.org/schema/mule/core"
	xmlns:doc="http://www.mulesoft.org/schema/mule/documentation"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd
http://www.mulesoft.org/schema/mule/http
http://www.mulesoft.org/schema/mule/http/current/mule-http.xsd
http://www.mulesoft.org/schema/mule/tradacoms
http://www.mulesoft.org/schema/mule/tradacoms/current/mule-tradacoms.xsd
http://www.mulesoft.org/schema/mule/ee/core
http://www.mulesoft.org/schema/mule/ee/core/current/mule-ee.xsd">
	<http:listener-config name="HTTP_Listener_config" doc:name="HTTP Listener config">
		<http:listener-connection host="localhost" port="8081" />
	</http:listener-config>
	<tradacoms:config name="TRADACOMS_EDI_Config" doc:name="TRADACOMS EDI Config">
		<tradacoms:schemas>
			<tradacoms:schema value="/tradacoms/ORDERS.esl"/>
			<tradacoms:schema value="/tradacoms/PROINF.esl"/>
		</tradacoms:schemas>
	</tradacoms:config>
	<flow name="tradacomsmappingFlow">
		<http:listener doc:name="Listener" config-ref="HTTP_Listener_config" path="/map"/>
		<tradacoms:read doc:name="Read" config-ref="TRADACOMS_EDI_Config"/>
		<ee:transform doc:name="Transform Message">
			<ee:message >
				<ee:set-payload ><![CDATA[%dw 2.0
output application/java
---
{
	STX: payload.STX,
	Id: payload.Id,
	ORDERS: payload.ORDERS
}]]></ee:set-payload>
			</ee:message>
		</ee:transform>
		<tradacoms:write doc:name="Write"/>
	</flow>
</mule>

XML の設定オプションについて

Anypoint Studio 設定にあるすべての値は XML で直接設定できます。

一般パラメータでは、送信と受信の両方のドキュメント処理を制御します (すべてが省略可能)。

XML 値 Visual Studio オプション

schemas=値のリスト

コネクタで使用するスキーマのパスのリスト。ファイルシステムまたはクラスパスのどちらかのパスを使用できます。

partnerCode

パートナーを識別するためのコード。この値を指定すると、受信したトランスミッションの Transmission Sender Code (トランスミッション送信者コード) の検証と、送信するトランスミッションの Transmission Recipient Code (トランスミッション受信者コード) の設定に使用されます (マップデータで指定されていない場合のみ)。指定しないと、受信したトランスミッションの Transmission Sender Code (トランスミッション送信者コード) は検証されません。

partnerName=パートナーを識別するための名前。この値を指定すると、受信したトランスミッションの Transmission Sender Name (トランスミッション送信者名) の検証と、送信するトランスミッションの Transmission Recipient Name (トランスミッション受信者名) の設定に使用されます (マップデータで指定されていない場合のみ)。指定しないと、受信したトランスミッションの Transmission Sender Name (トランスミッション送信者名) は検証されません。

selfCode

Mule アプリケーションを識別するためのコード。この値を指定すると、受信したトランスミッションの Transmission Recipient Code (トランスミッション受信者コード) の検証と、送信するトランスミッションの Transmission Sender Code (トランスミッション送信者コード) の設定に使用されます (マップデータで指定されていない場合のみ)。指定しないと、受信したトランスミッションの Transmission Recipient Code (トランスミッション受信者コード) は検証されません。

selfName

パーサーパラメータでは、パーサー操作と、受信メッセージの拒否の原因となるエラー条件の種別を制御します (すべてが省略可能であり、デフォルト値は次に示すとおりです)。

XML 値 Visual Studio オプション

enforceLengthLimits="true"

受信値の最小長と最大長を適用します。

allowUnknownSegments="false"

メッセージ内の不明なセグメントを許可します。

allowUnusedSegments="false"

メッセージ内で「未使用」としてマークされたセグメントを許可します

enforceSegmentOrder="true"

メッセージ内のセグメント順序を適用します。

ライター操作を制御するライターパラメータ (すべて省略可能):

XML 値 Visual Studio オプション

sendSenderReference

トランスミッションを作成する際に使用される Default Sender’s Transmission Reference (デフォルトの送信者のトランスミッション参照)。

sendRecipientReference

トランスミッションを作成する際に使用される Default Sender’s Transmission Reference (デフォルトの送信者のトランスミッション参照)。

sendApplicationReference

トランスミッションを作成する際に使用される Default Application Reference (デフォルトのアプリケーション参照)。

sendPriorityCode

トランスミッションを作成する際に使用される Default Transmission Priority Code (デフォルトのトランスミッション優先度コード)。

スキーマの場所を設定する方法

Anypoint Studio XML ビューでスキーマの場所を設定できます。

Anypoint Studio で [Configuration XML (設定 XML)] をクリックして XML ビューに切り替えて、各ドキュメント種別の <http://edischema[edi:schema]> 要素を追加することで、含めたいすべてのスキーマのリストが含まれるように TRADACOMS EDI 設定を変更します。

<tradacoms-edi:config name="TRADACOMS_EDI__Configuration" identKeys="true" doc:name="TRADACOMS EDI: Configuration">
  <tradacoms-edi:schemas>
    <tradacoms:schema value="/tradacoms/ORDERS.esl"/>
  </tradacoms-edi:schemas>
</tradacoms-edi:config>

TRADACOMS EDI のグローバル要素を作成したら、スキーマと操作を設定します。

TRADACOMS のメッセージ構造について

このコネクタでは、正式な EDI メッセージ構造との間で TRADACOMS ドキュメントを読み書きできます。この構造は、DataWeave またはコードを使用して操作できる Java マップおよびリストの階層として表されます。各トランザクションには、上述のように、スキーマで定義された独自の構造があります。

メッセージ自体には以下のキーが含まれます (一部のキーは、以下に示すように、読み取り操作または書き込み操作のどちらかにのみ適用されます)。

キー名 説明

{File}

メッセージデータのラッパーと、コンポーネントメッセージ名と一致し、これらのメッセージのデータにリンクするキー。ファイルの反復詳細メッセージ (常にクラス '2') の値はマップのリストとなり、ファイルの単一メッセージについて、値はマップとなります。

Errors (参照のみ)

入力メッセージに関連付けられたエラーのリスト。(下記「TRADACOMS EDI メッセージの読み込みと検証の方法」セクションの TradacomsError 構造の説明を参照してください。)

Id

ファイル (読み込んだ TRADACOMS ファイルの名前)。

STX

ファイルの先頭からの STX セグメントデータのマップ。

各メッセージは、ファイル名マップの下にそれぞれのマップと、そのメッセージのセグメントと一致するキーを持ちます。たとえば、INVOIC ファイルはルートマップに 'INVOIC' というキーを持ち、その下に 'INVFIL''INVOIC' (INVOIC メッセージの反復用データリスト)、'VATTLR'、および 'INVTLR' 用のキーがあります。INVTLR マップ内には、INVTLR メッセージのセグメント用の '01_MHD''02_TOT'、and '03_MTR' キーがあります。

TRADACOMS EDI メッセージの読み込みと検証の方法

  1. TRADACOMS メッセージを読み込むには、パレットで TRADACOMS EDI を検索し、TRADACOMS Read 操作をフローにドラッグします。

  2. プロパティビューに移動し、上で作成したコネクタ設定を選択して、Read 操作を選択します。この操作では、任意のバイトストリームを、TRADACOMS スキーマで記述された構造に読み取ります。

TRADACOMS モジュールはメッセージの読み取り時にメッセージを検証します。メッセージ検証では、ファイル内の実際のメッセージだけでなく、エンベロープセグメントの STX および END の構文とコンテンツを確認します。通常、エラーは記録および蓄積され、TradacomsError インスタンスでレポートされます。受け入れられたすべてのメッセージは (エラーがなくても、または致命的でないエラーであっても) 渡されて、出力メッセージマップの一部として処理されます。致命的なエラーが見つかった場合は、ファイルデータは返されず、エラーリストのみが返されます。

受信データのマップに入力されたエラーデータは、以下のプロパティを持つ参照のみの JavaBean である EdifactError クラスを使用します。

プロパティ 説明

segment

エラーの原因となったセグメントの入力内の開始値 0 のインデックス。

fatal

致命的なエラーのフラグ。ファイルデータは返されません。

errorText

エラーの説明

エラーデータは、データ構造のルートレベルとメッセージレベルの両方で、「Errors」キーを持つ省略可能なリストとして読み取り操作によって返されます。メッセージレベルでは、このリストには、そのメッセージの解析中に発生した致命的でないエラーが含まれます。ルートレベルでは、このリストには、インターチェンジエラーと致命的なメッセージエラーの両方が含まれます。

TRADACOMS EDI メッセージの作成方法

送信メッセージを作成するには、パレットで TRADACOMS EDI を検索し、TRADACOMS Write 操作をフローにドラッグします。前述の構造に従って送信 TRADACOMS EDI メッセージを作成し、書き込み操作への入力を定義します。書き込み操作で致命的なエラーが見つからない場合、通常のフローが続行されます。それ以外の場合、エラーを記述する例外がスローされます。

Was this article helpful?

💙 Thanks for your feedback!

Edit on GitHub