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)を引数に与えること
です。
順に確認していきましょう。
GoogleAppsScriptのXML文書パースでは、XML文書がValidであることが必要です。Validとは、DTDにきちんと基づいているということですね。
var entries = rootDoc.getChild('Body', nsSoapenv).getChild('loginResponse', nsDefault)
のように、Rootから順に要素をたどって目的の要素まで行きます。
その要素が1つの定義ならgetChild()、リスト形式で複数ならgetChildren()を使います。
上記の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());
}