[Delphi] TXMLDocumentでBOM無しLF改行のXMLファイルを出力する

本投稿は [Delphi AdventCalender 2023 #11](https://qiita.com/advent-calendar/2023/delphi) の記事です。

## はじめに
TXMLDocumentを利用すると，Delphiで簡単にXMLが利用できます。XMLはヘッダでUTF-8を指定すれば日本語のエンコードを気にする必要がありません。また，WindowsだけでなくMacOSやLinuxでも利用できます。
LinuxではUTF-8はBOM(Byte Order Mark)無しで，改行はLF(#10)を使うのが一般的なのでそれに合わせた出力を求められることがありますが，TXMLDocumentだけではそれに対応することができませんでした。
そこで，TStringListと組み合わせてBOM無しLF改行のXMLを出力する方法に挑戦してみました。

## SaveToFileで保存する
まずは，TXMLDocumentのSaveToFileを使います。Windows VCLアプリケーションを新規作成し，```Button```と```XMLDocument```をフォームに配置します。```Button1```のOnClickイベントを追加し，以下のようにコードを書きます。
**※ 保存するファイル名は保存できる場所に必ず書き換えてください。**

```unit1.pas
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Xml.xmldom, Xml.XMLIntf, Xml.XMLDoc,
  Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    XMLDocument1: TXMLDocument;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

const
  XML_Sample = // XMLのサンプルです
  '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>'+
  '<test attr="sample">'+
  '  日本語body'+
  '</test>';

procedure TForm1.Button1Click(Sender: TObject); // Button1のOnClickイベント
var
  lStrStream: TMemoryStream;
  st:TStringList;
begin
  XMLDocument1.XML.text := XML_sample;   // XMLDocument1にサンプルを代入します
  XMLDocument1.Active := True;           // アクティブにしないと保存できないです
  XMLDocument1.SaveToFile('sample.xml'); // ファイル名は適切な場所に変更してください
end;

end.
```
プログラムを実行してButton1をクリックすると，sample.xmlが保存されます。
テキストエディタで開くと，このXMLがBOM無のCRLF改行のUTF-8で保存されていることが分かります。エンコードを指定しなくてもBOM無UTF-8で保存できることが分かりました。

## TStringListを使う
しかし，改行をLFに変えたいのですが，TXMLDocumentに改行コードを変更する方法をみつけることができませんでした。そこで，TXMLDocumentの内容をTMemoryStreamでTStringListに渡して出力することにしました。```TForm1.Button1Click```を以下のように書き換えます。
**※ 保存するファイル名は保存できる場所に必ず書き換えてください。**
```unit1.pas
procedure TForm1.Button1Click(Sender: TObject);
var
  LMemoryStream: TMemoryStream;
  st:TStringList;
begin
  XMLDocument1.XML.text := XML_sample;
  XMLDocument1.Active := True;

  LMemoryStream:=TMemoryStream.Create;
  XMLDocument1.SaveToStream(LMemoryStream);
  LMemoryStream.Position:=0;
  st:=TStringList.Create;
  st.LoadFromStream(LMemoryStream,TEncoding.UTF8);
  LMemoryStream.Free;
  st.LineBreak := #10;              // 改行をLF #10 にする
  st.WriteBOM := False;             // BOMを出力しない デフォルトはTrue
  st.SaveToFile('sample2.xml',TEncoding.UTF8); // UTF-8で出力 ファイル名は適切な場所に変更してください
  st.Free;
end;
```
MemoryStreamからデータを受け取るには```Position := 0;```にしてから，```LoadStream```で```TEncoding.UTF8```でエンコードをUTF-8にする必要があります。
```TStringList```は，デフォルトでBOMを出力しますので，```WriteBOM:= False;```を指定する必要があります。
改行は```LineBreak```で改行コードを指定します。
UTF-8で保存するため```SaveToFile```で```TEncoding.UTF8```を指定しています。

実行してButton1をクリックするとXMLファイルが出力されます。テキストエディタで開くと，UTF-8でBOM無しLF改行のXMLファイルであることが確認できます。

## おまけ1: 行末の改行の設定を変更する
TStringListで保存されたファイルの最後(EOF)の前は改行ですが，改行をつけたくない場合もあるかもしれません。
その時には以下のように```TrailingLineBreak```プロパティを指定すると可能です。
```pascal
  st.TrailingLineBreak := False;
```

## おまけ2: 複数行の文字列(Delphi12以降)
Delphi 12から，複数行の文字列を簡単に記述できるようになりました。XML_sample定数は以下のように書くことができます。
```pascal
  XML_Sample = '''
  <?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
  <test attr="sample">
    日本語body
  </test>
  ''';
```
Qiitaはもう対応しているんですね。

## まとめ
```TMXLDocument```の出力をBOM無しLF改行でUTF-8で出力するには，```TMemoryStream```を介して```TStringList```を使い，```WriteBOM```プロパティや```LineBreak```プロパティを指定すると出力できることが分かりました。
この情報が皆様のお役に立てば幸いです。間違いがございましたらお知らせください。

### 参考資料
* [DocWiki Xml.XMLDoc.TXMLDocument](https://docwiki.embarcadero.com/Libraries/Athens/ja/Xml.XMLDoc.TXMLDocument)

* [DocWiki System.Classes.TStringList](https://docwiki.embarcadero.com/Libraries/Athens/ja/System.Classes.TStringList)