blog
ブログ

Salesforce技術ブログ:Excel VBA でカスタム表示ラベルの一覧を取得する

表計算

こんにちは!

今回は、Excel VBA を使ってカスタム表示ラベルの一覧を出力してみたいと思います。

※最近関わったプロジェクトでカスタム表示ラベル(多言語)を1000個以上使っており、こんなツールがあればなぁと思っていました。

早速調べてみましたが、カスタム表示ラベルを直接取得する API は見当たらず、仕方なくメタデータから引っこ抜く事にしました。

処理の流れは以下の様になります。

 1. ログイン
 2. メタデータ(カスタム表示ラベル)取得
 3. メタデータの展開
 4. カスタム表示ラベル取得情報の読み込み
 5. カスタム表示ラベル情報を出力

 

0.はじめに

 

VBA の参照設定について

今回のマクロは Excel2016 で作成し、処理の中で以下のライブラリを使用しました。Windows標準のライブラリなのでどのバージョンでも動作すると思います。 VBAエディタメニュー > ツール > 参照設定

 ・Microsoft Scripting Runtime
 ・Microsoft XML vX.0
 ・Microsoft Shell Controls And Automation

 

API の呼び出しについて

処理に入る前に、処理の中で何度か API を呼び出しますので、API 呼び出し用の部品を作っておきます。 呼び出しは HTTP通信で行います。ヘッダ情報は以下の通り。

soapaction: ""
Content-Type: text/xml;charset=UTF-8

  これを踏まえて、API 呼び出し用の部品を作っておきます。

' ****
' * HTTP 通信処理
' *
' * amUrl: URL
' * amSendXml: 送信する XML 文字列
' * aoResponseXml: 受け取った XML (OUTPUT)
' * return: True: 成功/False: 失敗
' ****
Private Function pbRequestHttp( _
    ByVal amUrl As String, _
    ByVal amSendXml As String, _
    ByRef aoResponseXml As MSXML2.DOMDocument _
) As Boolean
    Dim doHttp  As New MSXML2.XMLHTTP

    pbRequestHttp = False

    doHttp.Open "post", amUrl, False
    doHttp.setRequestHeader "Content-Type", "text/xml;charset=UTF-8"
    doHttp.setRequestHeader "SOAPAction", """"""
    doHttp.send amSendXml

    If doHttp.Status <> 200 Then Exit Function

    Set aoResponseXml = doHttp.responseXML
    pbRequestHttp = True

End Function

 

1.ログイン

  まず、Salesforce にログインし、セッション情報を取得する必要があります。 パラメータには以下の XML を設定します。

<?xml version="1.0" encoding="utf-8"?>
<env:Envelope   >
  <env:Body>
    <n1:login >
      <n1:username>#username#</n1:username>
      <n1:password>#password#</n1:password>
    </n1:login>
  </env:Body>
</env:Envelope>

  ログインに成功すると、以下のようなレスポンスが返ってきます。

<soapenv:Envelope   >
  <soapenv:Body>
    <loginResponse>
      <result>
        <metadataServerUrl>https://**********</metadataServerUrl>
        <passwordExpired>false</passwordExpired>
        <sandbox>false</sandbox>
        <serverUrl>https://**********</serverUrl>
        <sessionId>**********</sessionId>
        <userId>**********</userId>
        <userInfo>...</userInfo>
      </result>
    </loginResponse>
  </soapenv:Body>
</soapenv:Envelope>

  レスポンスから sessionId, metadataServerUrl を取得し、次の処理を行います。 VBA ソースは以下の通り。

    Dim dmXml   As String
    Dim doXml   As MSXML2.DOMDocument

    ' **** ログイン処理 ****
    ' ログイン用 XML 作成
    dmXml = Constants.XML_LOGIN
    dmXml = Replace(dmXml, "#username#", "**********")
    dmXml = Replace(dmXml, "#password#", "**********")

    ' ログイン URL
    ' ※ SandBox の場合は、https://test.salesforce.com/services/Soap/u/41.0
    Dim dmLoginUrl  As String
    dmLoginUrl = "https://login.salesforce.com/services/Soap/u/41.0"

    ' ログイン
    If Not pbRequestHttp(dmLoginUrl, dmXml, doXml) Then Exit Sub

    ' セッションID、MetadataApi 用 URL を取得
    Dim dmSessionId As String
    Dim dmMetadataServerUrl As String
    dmSessionId = doXml.getElementsByTagName("sessionId").Item(0).Text
    dmMetadataServerUrl = doXml.getElementsByTagName("metadataServerUrl").Item(0).Text

 

2.メタデータ(カスタム表示ラベル)取得

  次にカスタム表示ラベルを含むメタデータを取得します。メタデータ API の retrieve() をコールします。 パラメータには以下の XML を設定します。

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope   >
  <soap:Header>
    <SessionHeader >
      <sessionId>#sessionId#</sessionId>
    </SessionHeader>
  </soap:Header>
  <soap:Body>
    <retrieve >
      <retrieveRequest>
        <apiVersion>41.0</apiVersion>
        <singlePackage>true</singlePackage>
        <unpackaged>
          <version>41.0</version>
          <types>
            <name>CustomLabels</name>
            <members>*</members>
          </types>
          <types>
            <name>Translations</name>
            <members>*</members>
          </types>
        </unpackaged>
      </retrieveRequest>
    </retrieve>
  </soap:Body>
</soap:Envelope>

  今回はカスタム表示ラベルとその翻訳情報を取得するので types に CustomLabels, Translations を指定していますが、この types に指定する内容によって、Apex コードやオブジェクト、プロファイル情報等、さまざまなメタデータを取得することができます。 コール後、以下のようなレスポンスが返ってきます。

<?xml version="1.0"?>
<soapenv:Envelope  >
  <soapenv:Body>
    <retrieveResponse>
      <result>
        <done>false</done>
        <id>**********</id>
        <state>Queued</state>
      </result>
    </retrieveResponse>
  </soapenv:Body>
</soapenv:Envelope>

  メタデータ取得の処理は時間が掛かる事があるため、非同期で実行されます。retrieve() コール時に salesforce 側で処理が開始され、id が返ってきます。この id を元に、処理状況を確認する API checkRetrieveStatus() を定期的にコールし、処理が完了したかを確認します。 checkRetrieveStatus() のコールには以下の XML を設定します。

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope   >
  <soap:Header>
    <SessionHeader >
      <sessionId>#sessionId#</sessionId>
    </SessionHeader>
  </soap:Header>
  <soap:Body>
    <checkRetrieveStatus >
      <id>#id#</id>
    </checkRetrieveStatus>
  </soap:Body>
</soap:Envelope>

  処理が完了すると、以下のようなレスポンスが返ってきます。

<soapenv:Envelope  >
  <soapenv:Body>
    <checkRetrieveStatusResponse>
      <result>
        <done>true</done>
        <id>**********</id>
        <status>Succeeded</status>
        <success>true</success>
        <zipFile>****************************************</zipFile>
        …
      </result>
    </checkRetrieveStatusResponse>
  </soapenv:Body>
</soapenv:Envelope>

  done の値を確認し、true であれば処理完了です。false の場合はまだ処理中ですので、再度 checkRetrieveStatus() をコールし、処理が完了するまで繰り返します。success の値を確認し、処理が成功 (true) したかを判断します。処理が成功した場合、メタデータが ZIP ファイル(base64 エンコード)で返されます。次はこの zipFile の値を処理します。 ここまでの VBA ソースは以下の通り。

    ' **** メタデータ(カスタム表示ラベル)取得処理 ****
    ' メタデータ取得用 XML 作成
    dmXml = Constants.XML_CUSTOM_LABELS
    dmXml = Replace(dmXml, "#sessionId#", dmSessionId)

    ' メタデータ取得 API 呼び出し(非同期)
    If Not pbRequestHttp(dmMetadataServerUrl, dmXml, doXml) Then Exit Sub

    ' API コールの ID を取得
    Dim dmId    As String
    dmId = doXml.getElementsByTagName("id").Item(0).Text

    ' メタデータ取得 API 状況確認用 XML 作成
    dmXml = Constants.XML_CHECK_RETRIEVE_STATUS
    dmXml = Replace(dmXml, "#sessionId#", dmSessionId)
    dmXml = Replace(dmXml, "#id#", dmId)
    
    ' メタデータ取得 API 状況確認
    Dim dbDone  As Boolean
    Do
        Sleep 1000
        If Not pbRequestHttp(dmMetadataServerUrl, dmXml, doXml) Then Exit Sub
    
        ' done 項目が True で完了
        dbDone = CBool(doXml.getElementsByTagName("done").Item(0).Text)
    Loop Until dbDone

    ' メタデータ取得 API 結果確認
    Dim dbSuccess   As Boolean
    dbSuccess = CBool(doXml.getElementsByTagName("success").Item(0).Text)
    If Not dbSuccess Then Exit Sub

    ' メタデータ取得(ZIP ファイル base64 エンコード)
    Dim dmZipData   As String
    dmZipData = doXml.getElementsByTagName("zipFile").Item(0).Text

  ※ Do Loop 内で “Sleep 1000” とありますが、これは WindowsApi を使用しています。(1秒待機) モジュール冒頭で以下を定義しています。

   Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal alMsec As LongPtr)

 

3.メタデータの展開

  次は取得した ZIP ファイル(base64 エンコード)を処理し、メタデータのファイルを取り出します。 VBA での base64 のデコード処理はこんな感じです。

' ****
' * base64 デコード処理
' *
' * amString: base64 エンコード文字列
' * return: デコードデータ(バイナリ)
' ****
Private Function pbDecodeBase64(ByVal amString As String) As Byte()

    Dim doXmlDoc As Object
    Dim doElm As Object

    Set doXmlDoc = CreateObject("MSXML2.DOMDocument")

    ' base64 to byte array
    Set doElm = doXmlDoc.createElement("b64")
    doElm.DataType = "bin.base64"
    doElm.Text = amString

    pbDecodeBase64 = doElm.nodeTypedValue

End Function

  デコード後は、ファイルに出力⇒解凍の流れとなります。ZIP ファイルの解凍は Shell32 ライブラリを使って行います。ここまでの処理を踏まえて、ZIP ファイルの解凍処理は以下のようになります。

' ****
' * ZIP ファイル解凍処理
' *
' * amZipData: ZIP ファイルデータ(base64 エンコード文字列)
' * amUnzipRootFolder: ZIP ファイル解凍先フォルダ (OUTPUT)
' ****
Private Sub pUnzipData(ByVal amZipData As String, ByRef amUnzipRootFolder As String)

    ' base64 デコード
    Dim dvZipBin()  As Byte
    dvZipBin = pbDecodeBase64(amZipData)

    ' 出力する ZIP ファイルのパスを作成(テンポラリフォルダに作成)
    Dim doFso   As New Scripting.FileSystemObject
    Dim doTempFolder    As Scripting.Folder
    Dim dmZipFile   As String
    Set doTempFolder = doFso.GetSpecialFolder(TemporaryFolder)
    Do
        dmZipFile = doFso.BuildPath(doTempFolder.Path, doFso.GetTempName()) & ".zip"
    Loop While doFso.FileExists(dmZipFile)

    ' ZIP ファイルを出力
    Dim diFileNo    As Integer
    diFileNo = FreeFile()
    Open dmZipFile For Binary As #diFileNo
    Dim i   As Long
    For i = 0 To UBound(dvZipBin)
        Put #diFileNo, , dvZipBin(i)
    Next i
    Close #diFileNo

    ' ZIP ファイル解凍先フォルダの作成(テンポラリフォルダに作成)
    Do
        amUnzipRootFolder = doFso.BuildPath(doTempFolder.Path, doFso.GetTempName())
    Loop While doFso.FolderExists(amUnzipRootFolder)
    doFso.CreateFolder amUnzipRootFolder

    ' ZIP ファイル解凍
    Dim doShell As New Shell32.Shell
    Dim doZip   As Shell32.Folder
    Set doZip = doShell.Namespace(dmZipFile)
    Dim doUnzipFolder   As Shell32.Folder
    Set doUnzipFolder = doShell.Namespace(amUnzipRootFolder)
    doUnzipFolder.CopyHere doZip.Items

    ' ZIP ファイルを削除
    doFso.DeleteFile dmZipFile

End Sub

  メイン処理

    ' **** メタデータの展開 ****
    'ZIP ファイルを解凍
    Dim dmUnzipRootFolder   As String
    pUnzipData dmZipData, dmUnzipRootFolder

 

4.カスタム表示ラベル取得情報の読み込み

  ここまで来ればあとは、メタデータを読み取って Excel に出力するだけです。 試しに以下のカスタム表示ラベルを作成してみました。 ZIP ファイルを解凍したメタデータは以下の構成になっています。  

フォルダ構成

|-- labels
|   +-- CustomLabels.labels
|-- translations
|   +-- en_US.translation
+-- package.xml

※今回は翻訳設定が英語のみなので translation は en_US のみですが、 複数言語を設定している場合、言語分ファイルが作成されます。  

CustomLabels.labels

<?xml version="1.0" encoding="UTF-8"?>
<CustomLabels >
    <labels>
        <fullName>LBL_APPLE</fullName>
        <categories>果物</categories>
        <language>ja</language>
        <protected>true</protected>
        <shortDescription>説明1</shortDescription>
        <value>りんご</value>
    </labels>
    <labels>
        <fullName>LBL_CHERRY</fullName>
        <categories>果物</categories>
        <language>ja</language>
        <protected>true</protected>
        <shortDescription>説明3</shortDescription>
        <value>さくらんぼ</value>
    </labels>
    <labels>
        <fullName>LBL_MELON</fullName>
        <categories>果物</categories>
        <language>ja</language>
        <protected>true</protected>
        <shortDescription>説明2</shortDescription>
        <value>メロン</value>
    </labels>
</CustomLabels>

 

en_US.translation

<?xml version="1.0" encoding="UTF-8"?>
<Translations >
    <customLabels>
        <label>Apple</label>
        <name>LBL_APPLE</name>
    </customLabels>
    <customLabels>
        <label>Cherry</label>
        <name>LBL_CHERRY</name>
    </customLabels>
    <customLabels>
        <label>Melon</label>
        <name>LBL_MELON</name>
    </customLabels>
</Translations>

  VBA はこんな感じで組みました。

    ' **** カスタム表示ラベル取得情報の読み込み ****
    ' トランスレーション(翻訳)情報の読み込み
    Dim doFso   As New Scripting.FileSystemObject
    Dim doFile  As Scripting.File
    Dim dcTranslations  As New Scripting.Dictionary

    If doFso.FolderExists(dmUnzipRootFolder & "translations") Then
        ' translations フォルダ配下のファイルを読み込み
        For Each doFile In doFso.GetFolder(dmUnzipRootFolder & "translations").Files
            doXml.Load doFile.Path
        
            Dim doNode  As MSXML2.IXMLDOMNode
            Dim dmTranslation   As String
            dmTranslation = doFso.GetFileName(doXml.Url)
            For Each doNode In doXml.getElementsByTagName("customLabels")
                Dim dmName  As String
                Dim dmLabel As String
                dmName = doNode.SelectSingleNode("name").Text
                dmLabel = doNode.SelectSingleNode("label").Text
            
                If dmLabel <> "" Then
                    If Not dcTranslations.Exists(dmName) Then dcTranslations.Add dmName, New Scripting.Dictionary
                    dcTranslations(dmName)(dmTranslation) = dmLabel
                End If
            Next doNode
        
        Next doFile
    End If

    ' カスタム表示ラベル情報の読み込み
    Dim dcRecords   As New Collection
    Dim dcRecord    As Scripting.Dictionary
    Dim dcFieldInfo As Scripting.Dictionary
    Dim doField As MSXML2.IXMLDOMNode
    Dim dvKey   As Variant

    Set dcFieldInfo = New Scripting.Dictionary
    
    ' CustomLabels.labels の読み込み
    doXml.Load dmUnzipRootFolder & "labelsCustomLabels.labels"

    For Each doNode In doXml.getElementsByTagName("labels")
        Set dcRecord = New Scripting.Dictionary
        For Each doField In doNode.ChildNodes
            dcRecord.Add doField.nodeName, doField.Text
        Next doField

        If dcTranslations.Exists(dcRecord("fullName")) Then
            For Each dvKey In dcTranslations(dcRecord("fullName")).Keys
                dcRecord.Add dvKey, dcTranslations(dcRecord("fullName"))(dvKey)
            Next dvKey
        End If
        
        dcRecords.Add dcRecord
    Next doNode

    ' メタデータの削除
    doFso.DeleteFolder dmUnzipRootFolder

 

5.カスタム表示ラベル情報を出力

  Excel シートに出力します。VBA はこんな感じです。

    ' **** カスタム表示ラベル情報を出力 ****
    Dim doRange As Excel.Range
    Dim doCell  As Excel.Range
    Dim dvRange As Variant
    Dim i   As Long
    Dim j   As Long
    Set doCell = Application.ActiveCell
    Set doRange = Application.Range(doCell, doCell.Offset(dcRecords.Count, dcRecords(1).Count - 1))
    dvRange = doRange

    ' 表ヘッダ行を設定
    Set dcRecord = dcRecords(1)
    For i = 0 To dcRecords(1).Count - 1
        dvRange(1, i + 1) = dcRecord.Keys(i)
    Next i

    ' カスタム表示ラベル情報を設定
    For i = 1 To dcRecords.Count
        Set dcRecord = dcRecords(i)
        For j = 1 To dcRecords(1).Count
            dvRange(i + 1, j) = dcRecord(dvRange(1, j))
        Next j
    Next i

    ' シートに出力
    doRange = dvRange

  できました! という事で今回はカスタム表示ラベルを出力させる事ができました。 次は、オブジェクト定義の出力や Excel 上でのデータの更新・削除にチャレンジしたいと思います。

contact

ご相談・ご質問等ございましたら、お気軽にお問い合わせください。

翻訳

SFA/CRMに蓄積されている情報を活用する方法ガイド