0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Cloud IntegrationでSplitter & Gatherパターンを使ってCSVファイルを編集

Last updated at Posted at 2025-10-17

やりたいこと

CSVファイルを受け取り、リモートAPI呼び出しで追加の項目を取得し、元のCSVフォーマットに項目を追加して返します。

image.png

CSVファイルには複数行あり、リモートAPI呼び出しはレコードごとに実行する必要があります。そこでSplitterでレコードごとに分割してAPIを呼び出し、Gatherで処理結果をまとめて、そのあと最終的なCSVファイルを出力します。

以下がインプットとアウトプットのサンプルです。

インプット
ProductID,ProductName
1,Chai
2,Chang
3,Aniseed Syrup
アウトプット
ProductID,ProductName,SupplierID
1,Chai,1
2,Chang,1
3,Aniseed Syrup,1

作成したフロー

以下のフローを作成しました。上のフローがメインの処理で、下のフロー(ローカルプロセス)では1行ずつのレコードを処理します。

image.png

1. メインのフロー

1.1. CSV to XML Converter

HTTPリクエストで受け取ったCSVデータをXMLに変換します。これは、後続の処理でXMLのほうが扱いやすいためです。

image.png

XML Schemaには以下のスキーマを(ChatGPTに依頼して)作成し、アップロードしました。

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
           elementFormDefault="qualified">

  <xs:element name="root">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="Product" maxOccurs="unbounded" minOccurs="1">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="ProductID" type="xs:integer"/>
              <xs:element name="ProductName" type="xs:string"/>
            </xs:sequence>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>

</xs:schema>

インプットのCSVは以下のXMLに変換されます。

<?xml version='1.0' encoding='UTF-8'?>
<root>
	<Product>
		<ProductID>1</ProductID>
		<ProductName>Chai</ProductName>
	</Product>
	<Product>
		<ProductID>2</ProductID>
		<ProductName>Chang</ProductName>
	</Product>
	<Product>
		<ProductID>3</ProductID>
		<ProductName>Aniseed Syrup</ProductName>
	</Product>
</root>

1.2. Iterating Splitter

後続処理をProductの単位でおこなうため、Iterating Splitterを使用します。以下が一度に処理されるレコードの例です。

	<Product>
		<ProductID>1</ProductID>
		<ProductName>Chai</ProductName>
	</Product>

Iterating Splitterを以下のように設定します。Parallel Processingにチェックを入れることで並列処理が可能になりますが、今回は使用していません。

image.png

SplitterにはGeneral SplitterIterating Splitterがあります。対象のノードの上位の階層(今回のケースでは<root>)も維持したまま分割したい場合はGeneral Splitter、上位の階層が不要な場合はIterating Splitterを使用します。

参考:General and Iterating Splitter

1.3. Process Call

事前に定義したローカルプロセス(Process Recordという名前)を呼び出すためのステップです。

image.png

1.4. Gather

ローカルプロセスから返ってくるデータは以下の形式になっています。

<?xml version="1.0" encoding="UTF-8"?>
<Product>
	<ProductID>1</ProductID>
	<ProductName>Chai</ProductName>
	<SupplierID>1</SupplierID>
</Product>
<?xml version="1.0" encoding="UTF-8"?>
<Product>
	<ProductID>2</ProductID>
	<ProductName>Chang</ProductName>
	<SupplierID>1</SupplierID>
</Product>
<?xml version="1.0" encoding="UTF-8"?>
<Product>
	<ProductID>3</ProductID>
	<ProductName>Aniseed Syrup</ProductName>
	<SupplierID>1</SupplierID>
</Product>

Gatherステップで分割されたレコードを再び1つにまとめます。Aggregation AlgorithmにCombine at XPathを指定することで、結合元になるノード(Combine from source (XPath))とその上位になるノード(Combine at target (XPath))を選択できます。

image.png

参考:Define Gather and Join

結果は以下のようになります。

image.png

1.5. XML to CSV Converter

XMLをCSV形式に戻します。Path to Source Element in XMLには繰り返し現れる要素(ここでは/Products/Product)を指定します。

image.png

//Productではうまくいきませんでした。ここでは明示的にパスを指定する必要があります。

1.6. Content Modifier

レスポンスヘッダにContent-Type: text/csvを設定します。
image.png

2. ローカルプロセス

2.1. Content Modifier

リモートAPIを呼び出す前に、もともとのボディをoriginalBodyという名前でExchange Propertyに退避します。また、API呼び出し時に使用するためProductIDをproductIdという名前で設定します。

image.png

2.2. HTTP Adapter

リモートAPI(ここではNorthwindのOData V4 API)にアクセスして、ProductIDをキーにSuplierIDを取得します。

image.png

以下のようなレスポンスが返ってきます。

{
    "@odata.context": "https://services.odata.org/V4/Northwind/Northwind.svc/$metadata#Products(SupplierID)/$entity",
    "@odata.etag": "W/\"1,2\"",
    "SupplierID": 1
}

2.3. Groovy Script

Groovy Scriptで退避しておいたボディとAPIからのレスポンスを合わせたXMLを作成します。APIから取得する項目が増えても対応できるようにしています。

import com.sap.gateway.ip.core.customdev.util.Message
import groovy.xml.*
import groovy.json.JsonSlurper

Message processData(Message message) {

    // Propertyから originalBody を取得
    def originalBody = message.getProperty("originalBody") as String
    def productXml = new XmlParser().parseText(originalBody)

    // Body から JSON を取得
    def jsonText = message.getBody(String)
    def json = new JsonSlurper().parseText(jsonText)

    // originalBody に JSON のキー/値を追加
    json.each { key, value ->
        // ODataのメタ系キー(@odata...)はスキップ
        if(!key.startsWith("@")) {
            // 子要素を追加
            productXml.appendNode(key, value?.toString())
        }
    }

    // XMLをシリアライズ
    def xmlString = XmlUtil.serialize(productXml)    
    // タグ間の改行・空白を除去
    xmlString = xmlString.replaceAll('>\\s+<', '><')    
    
    message.setBody(xmlString)

    return message
}

出力は以下のようになります。

<?xml version="1.0" encoding="UTF-8"?>
<Product>
	<ProductID>3</ProductID>
	<ProductName>Aniseed Syrup</ProductName>
	<SupplierID>1</SupplierID>
</Product>
0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?