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 上でのデータの更新・削除にチャレンジしたいと思います。