Google Apps ScriptのXmlServiceでXMLをparseする(或いはSalesforceにGASから接続する方法)

Google Apps Script(GAS)では、httpのPOSTやGETを行うことができます。

ということは、GASからSalesforceにログインして、データを取ってきて、GoogleSpreadSheetで表示することも出来そうだなーと思って、ログイン処理からコーディングし始めたのですが、思ったよりも手こずりました。

特に、ログイン後、返却されるXMLをパースしてログイン用のトークンを抽出するところが、難しかったです。

RSSやWebサービスの戻りの解析など、XML文書のパースはいたるところで使いそうですが、きちんとまとめておかないと、そのたびにつまづきそうなので、ここにメモっておきます。

UrlFetchAppを使って、ログインURLへデータをPOSTする

はじめに、UrlFetchApp.fetch()を使って、POSTを行います。

fetchの引数のoptionsにはヘッダやメソッド、実際のデータなどをセットします。

データには上記のようなXML文書を生成して、セットします。「username」と「password」は自分の環境に合わせて適宜変更してください。

XML文書から、XmlServiceを使ってparseし、文字列抽出する

さて、上記のようにloginし、成功すると、以下のようなXML文書が返ってきます。

このXMLから目的の文字列を抽出するのがひと苦労なんですよね。。

今回は例として、データ取得の際にヘッダに埋め込むのが必要な「sessionId」を抽出してみたいと思います。

多分、他のXML文書で、なかなか目的の値を抽出できない人も同じように考えればOK かと。

キモは名前空間(NameSpace)です。

基本的には、

var xmlDoc = XmlService.parse(response.getContentText());
var rootDoc = xmlDoc.getRootElement();

でドキュメント全体の構造を取得したら、getChild() またはgetChildren()で目的の要素を抽出します。

このときに重要なのは、

  • Valid(妥当)なXML文書であること
  • Rootから順に要素をたどって目的の要素まを取得すること
  • 要素をgetChild() またはgetChildren()するときは、適切な名前空間(NameSpace)を引数に与えること

です。

順に確認していきましょう。

Valid(妥当)なXML文書であること

GoogleAppsScriptのXML文書パースでは、XML文書がValidであることが必要です。Validとは、DTDにきちんと基づいているということですね。

Rootから順に要素をたどって目的の要素まを取得すること

var entries = rootDoc.getChild('Body', nsSoapenv).getChild('loginResponse', nsDefault)

のように、Rootから順に要素をたどって目的の要素まで行きます。

その要素が1つの定義ならgetChild()、リスト形式で複数ならgetChildren()を使います。

要素をgetChild() またはgetChildren()するときは、適切な名前空間(NameSpace)を引数に与えること

上記のgetChild() には、第二引数で名前空間が渡されていることが分かります。

名前空間は、下の画像を例にすると、

の「soapenv」の部分です。では、soapenv名前空間の定義はどこにあるのかというと、XML文書の上の方を確認するとありますね。

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns="urn:partner.soap.sforce.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

の部分です。

xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"

ですね。

つまり、に囲まれた部分をgetChild()するには、第二引数に

var nsSoapenv = XmlService.getNamespace("soapenv", "http://schemas.xmlsoap.org/soap/envelope/");

のnsSoapenvを与える必要があります。

これを適切に与えないと、いくらやっても、getChild()/getChildren()の戻りが「null」になってしまいます。

以下のXML文書でいうと、それより下のタグには、名前空間がないですね。なので、デフォルトの(名前なし)の名前空間になります。

var entries = rootDoc.getChild('Body', nsSoapenv).getChild('loginResponse', nsDefault).getChild('result', nsDefault).getChild('sessionId', nsDefault);

上記のように、そこまで、順番にたどっていけばOKです。

いかがでしたでしょうか。

GoogleAppsScriptでのXML文書のパースは、結構頻出しますので、やり方を理解しておくと、いろいろなシーンに応用がきくと思いますよ。

ソースはこちら。

function myFunction() {
  
  var headers = { 
    "SOAPAction" : "login"
  };
 
  var data = "<?xml version=\"1.0\" encoding=\"utf-8\" ?><env:Envelope xmlns:xsd=\"http://www.w3.org/2001/XMLSchem\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:env=\"http://schemas.xmlsoap.org/soap/envelope/\"><env:Body><n1:login xmlns:n1=\"urn:partner.soap.sforce.com\"><n1:username>xxxxxxx@gmail.com</n1:username><n1:password>xxxxxxx7890</n1:password></n1:login></env:Body></env:Envelope>";
 
  var options =
   {
     "contentType" : "text/xml;charset=utf-8",
     "method" : "post",
     "headers" : headers,
     "payload" : data
   };
   
  var response = UrlFetchApp.fetch("https://login.salesforce.com/services/Soap/u/40.0", options);  
 
  Logger.log(response.getContentText());
   
  var xmlDoc = XmlService.parse(response.getContentText());
  var rootDoc = xmlDoc.getRootElement();
  
   
  var nsSoapenv = XmlService.getNamespace("soapenv", "http://schemas.xmlsoap.org/soap/envelope/");
  var nsDefault = XmlService.getNamespace("", "urn:partner.soap.sforce.com");
   
  var entries = rootDoc.getChild('Body', nsSoapenv).getChild('loginResponse', nsDefault).getChild('result', nsDefault).getChild('sessionId', nsDefault);
 
   
  Logger.log(rootDoc);
  Logger.log(entries.getText());
   
}