【Salesforce】Apexクラス＋Visualforceページを使用してExcel(xml)ファイル出力

ベースとして以下の記事を参考にしました。
[【Salesforce】Apex+VFでExcel帳票作成](https://qiita.com/masabit/items/aed9e0e9084d05401d8f)

## 前提
* Windows環境での説明となっております。
* Salesforce環境に以下のファイルを準備しているとします。
* * Visualforceページ「ExamplePage.txt」
* * Apexクラス「ExaplePageController.cls」

## 1. 型として使用したいExcelファイルをxml形式で保存する
ファイルの種類で「XML スプレッドシート(.xml)」を選択して保存します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3831415/083b5f93-cc40-5a06-f468-93b5531c6eb3.png)

## 2. Visualforceページ「ExamplePage.txt」の設定

```
<apex:page contenttype="text/xml;charset=UTF-8#ExamplePage.xml" controller="ExaplePageController">
<apex:outputText escape="true" value="{!xmlVer}" />
<apex:outputText escape="true" value="{!xmlApp}" />
<!-- Workbook以下XMLコード -->
<!-- ここに前項で保存したXMLファイルの内容を部分的に貼り付けます。 -->

</Workbook>
</apex:page>
```

### 貼り付けるXMLの内容
出力したXMLファイルには転記内容として不要な箇所も混じっているため、以下のようなファイルになるように修正します。
* 先頭の二行（「<?」から始まる行）は削除します
* 「<DocumentProperties」で始まるXMLファイル所有ユーザ情報の行も削除します。

整理すると以下のようなソースが残るので、こちらを「<!-- ここに前項で保存したXMLファイルの内容を部分的に貼り付けます。 -->」の部分にコピペします。
```
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
 xmlns:o="urn:schemas-microsoft-com:office:office"
 xmlns:x="urn:schemas-microsoft-com:office:excel"
 xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
 xmlns:html="http://www.w3.org/TR/REC-html40">
 <ExcelWorkbook xmlns="urn:schemas-microsoft-com:office:excel">
  <WindowHeight>7200</WindowHeight>
  <WindowWidth>18408</WindowWidth>
  <WindowTopX>0</WindowTopX>
  <WindowTopY>0</WindowTopY>
  <ProtectStructure>False</ProtectStructure>
  <ProtectWindows>False</ProtectWindows>
 </ExcelWorkbook>
 <Styles>
  <Style ss:ID="Default" ss:Name="Normal">
 ・
 ・
 ・
  </Worksheet>
</Workbook>
</apex:page>
```

## 3. Apexクラス「ExaplePageController.cls」の設定
下部にxml制御のためのメソッドを追加します。
```
public class ExamplePageController {
    public ExamplePageController() {
    // Visualforceページの表示制御に関するコード
    }

    public String getXmlVer() {
            return '<?xml version="1.0"?>' + '\r\n';
    }
    public String getXmlApp() {
        return '<?mso-application progid="Excel.Sheet"?>' + '\r\n';
    }
}

```

## 4. 「ファイルの破損」について
完成したファイルを出力してExcelで開こうとすると、以下のようにエラーが出ます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3831415/4a5bc4bc-363f-607c-479d-12b2d0f0c8e2.png)

### 対応ケース１：NumberFormatタグの使用をやめる
原因のひとつとして「NumberFormat」タグが考えられます。これは、例えば金額を入力するセルに対して「¥1,000」のように数詞を入れたり三桁ごとにカンマで区切るなど表示の設定を行っている箇所になります。

以下のNumberFormatタグでは、数字を3桁ごとにカンマで区切って通貨として表示するような設定になっているようです。
▼Visualforceページ「ExamplePage.txt」XML出力コピペの状態
```
  <Style ss:ID="s128" ss:Parent="s30">
   <Alignment ss:Vertical="Center" ss:ShrinkToFit="1"/>
   <Font ss:FontName="ＭＳ Ｐゴシック" x:CharSet="128" x:Family="Modern" ss:Size="9"
    ss:Color="#000000"/>
   <NumberFormat ss:Format="&quot;¥&quot;#,##0_);\(&quot;¥&quot;#,##0\)"/>
  </Style>
```

StyleのID(書式のID)が「s128」となっているので、Apexページ内を検索してこちらのスタイルが使用されている箇所を探します。
以下のような箇所が見つかれば使用されているということになります。
```
<Cell ss:StyleID="s128"><Data ss:Type="Number">0</Data></Cell>
```

対処方法についてですが、
①NumberFormatタグの内容を削除する
②Apexなどを用いて表示形式を変更してVisualforceページに送る
が考えられます。

また、この実践をしていて気づいたのですが、VisualforceページにてXMLを出力する方法を実装しApexで動的な値を出力させようとすると上手くいかないケースが多いです。
例えば以下のように「apex:repeat」や「apex:param」を表示しようと試みたのですが、どうしてもファイルが破損してしまいました。
```
    <apex:repeat value="{!formattedAmounts}" var="amount">
        <Cell ss:StyleID="s126">
            <Data ss:Type="String">{!amount}</Data>
        </Cell>
    </apex:repeat>
```

```
<apex:outputtext value="{0, number, ###,###}">
	<apex:param value="{!ItemNumber}"></apex:param>
</apex:outputtext>
```

## 「コンポーネント /apexcomponent/ss__data は存在しません」というエラー が出た場合
![ss__data は存在しません](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3831415/2102b6fa-e69e-fabd-13ea-65e892616f6c.png)
「ss:Data」でコード内を検索し、該当の記述を削除すると解消


## 結論
XML形式で記述する方法についての記事は少なく、公式からどのように書けばよいというような情報も現状見つかりません。
Visualforceページの中にXMLを記述するというのは決して推奨されているわけではない書き方として、出来ないことについては割り切るしかなさそうです...。