[XSLT]ADODB.RecordsetからHTMLの表を生成する[VBA]

## 概要

[XSLT](https://ja.wikipedia.org/wiki/XSL_Transformations)の勉強目的で、ADODB.RecordsetをHTMLのテーブルに変換する処理を作成したため、備忘録として残す。

記事内で使用しているファイルやコードは以下の場所にも保存している。

https://github.com/imihito/imihito_Sandbox/tree/master/XML/XSLTforADODB_Recordset

### 前提

- Windows上のVBA 7.1.1136 64bit(32bitでも問題ないはず)

以下のタイプライブラリを使用する。
- ADODB = Microsoft ActiveX Data Objects 6.1 Library
- MSXML2 = Microsoft XML, v6.0

XSLTのバージョンは1.0とする(=MSXMLが対応しているバージョン)。

### XSLT とは？

https://ja.wikipedia.org/wiki/XSL_Transformations

XMLを他の形式(XMLやHTML、テキスト)に変換する方法のこと。
テンプレートとなるXSLファイル(`*.xsl`)を用意し、それをXMLに適用することで結果の文字列を得ることができる。

## ADODB.RecordsetのXMLファイルの構造

XSLファイルの作成には、元となるXMLファイルの情報を知る必要がある。
この記事では、以下のVBAコードで作成したRecordsetのXMLファイルを前提とする。

### Recordset定義

```vb:Recordset作成
Sub Sample_CreateRecordset()
    
    'ADODB.Recordset の定義(データベースと接続しない場合)。
    Dim rs As ADODB.Recordset
    Set rs = VBA.Interaction.CreateObject("ADODB.Recordset")
    
    '列の名前と入る値の種類の定義。
        'サンプルの都合上、英数字の名前と日本語の名前としている。
    With rs.Fields
        .Append "Strings", adBSTR
        .Append "日付", adDate
    End With
    rs.Open
    
    'ADODB.Recordset に中身を設定。
        '1行目は Hoge\nFuga と 今日の日付
        '2行目は Piyo と 24/04/01 12:34:56
    rs.AddNew Array(0, 1), Array("Hoge" & vbLf & "Fuga", Date)
    rs.AddNew Array(0, 1), Array("Piyo", #1/23/2024 12:34:56 PM#)
    
    'ADODB.Recordset を XML として保存する。
    Dim domDoc As MSXML2.DOMDocument60
    Set domDoc = VBA.Interaction.CreateObject("MSXML2.DOMDocument.6.0")
    
    rs.Save domDoc, adPersistXML 'adPersistXML = 1
    
    'ファイルとして保存。
    domDoc.Save RecordsetのXMLの保存先
    
    'イミディエイトウィンドウでの確認用。
    Debug.Print domDoc.XML
End Sub
```

### Recordsetの情報

作成したRecordsetは以下のような表の情報を格納したものとなる。

|Strings	|日付|
|--|--|
|Hoge <br> Fuga	|2024-04-07T00:00:00|
|Piyo|	2024-01-23T12:34:56|

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142842/f31740a0-16b8-03d8-87f6-e85bdfd62635.png)


### RecordsetのXML
`domDoc.Save`で保存されるXMLファイルは以下のようになる。

```xml:Recordset.xml
<xml xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882" xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882" xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema">
	<s:Schema id="RowsetSchema">
		<s:ElementType name="row" content="eltOnly" rs:updatable="true">
			<s:AttributeType name="Strings" rs:number="1" rs:write="true">
				<s:datatype dt:type="string" dt:maxLength="4294967295" rs:precision="0" rs:long="true" rs:maybenull="false"/>
			</s:AttributeType>
			<s:AttributeType name="c1" rs:name="日付" rs:number="2" rs:write="true">
				<s:datatype dt:type="dateTime" rs:dbtype="variantdate" dt:maxLength="16" rs:precision="0" rs:fixedlength="true" rs:maybenull="false"/>
			</s:AttributeType>
			<s:extends type="rs:rowbase"/>
		</s:ElementType>
	</s:Schema>
	<rs:data>
		<rs:insert>
			<z:row Strings="Hoge&#xA;Fuga" c1="2024-04-07T00:00:00"/>
			<z:row Strings="Piyo" c1="2024-01-23T12:34:56"/>
		</rs:insert>
	</rs:data>
</xml>
```

ここで注目すべき点としては以下の2点となる。

- `xml/s:Schema/s:ElementType/s:AttributeType`のノードにフィールド名らしき情報(=表の見出し)があること
- `xml/rs:data//z:row`の属性として`rs.AddNew`で追加した情報(=表の中身)が含まれていること

## XSLファイルの定義

XSLファイルは、XSLTで変換するときのテンプレートとなるファイルのこと。
ここではRecordsetの情報をそのままHTMLの表にするためのXSLを定義する。

XSLファイルの使い方は以降に示す。

### XSLファイルの中身

```xml:stylesheet.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0"

    xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882"
    xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
    xmlns:rs="urn:schemas-microsoft-com:rowset"
    xmlns:z="#RowsetSchema"

    xmlns:my="MSXMLのXSLTでcdata-section-elementsを利用するための名前空間定義(任意の文字列で可)"
    exclude-result-prefixes="s dt rs z my"
>
    <xsl:output 
        method="xml"
        indent="yes"
        omit-xml-declaration="yes"
        encoding="utf-8"
        cdata-section-elements="my:cdata"
        media-type="text/html"
    />
    <xsl:template match="/">
<!-- <!DOCTYPE html>の出力用。-->
<xsl:text disable-output-escaping="yes">&lt;!DOCTYPE html&gt;&#10;</xsl:text>
<html lang="ja">
    <head>
        <meta charset="utf-8"/>
        <title>Recordset XML to HTML</title>
        <!-- HTMLファイル内にCSS 定義を含める(cdata-section-elementsのサンプル) -->
        <style text="text/stylesheet">/*<my:cdata><![CDATA[*/
table, th, td {
    border : solid thin; 
}
        /*]]></my:cdata>*/</style>
    </head>
    <body>
        <table>
            <!-- 表の見出しとしてRecordsetのフィールド名を出力 -->
            <thead>
                <tr>
                    <xsl:for-each select="xml/s:Schema/s:ElementType/s:AttributeType">
                    <th>
                        <!--
                            Recordsetのフィールド名に日本語などが含まれる場合、
                            @nameはc1などの表記となり、実際の名前は@rs:nameで定義される。
                            そのため、@rs:nameがある場合は@rs:nameを出力し、そうでない場合は@nameを出力している。
                        -->
                        <xsl:choose>
                            <xsl:when test="@rs:name">
                                <xsl:value-of select="@rs:name" />
                            </xsl:when>
                            <xsl:otherwise>
                                <xsl:value-of select="@name" />
                            </xsl:otherwise>
                        </xsl:choose>
                    </th>
                    </xsl:for-each>
                </tr>
            </thead>
            <tbody>
                <xsl:for-each select="xml/rs:data//z:row">
                <tr>
                    <xsl:for-each select="@*">
                    <td>
                        <!-- LF(&#xA;=改行)があった場合、brタグに置き換えるテンプレートを呼び出す。 -->
                        <xsl:call-template name="replace-text">
                            <xsl:with-param name="base-text" select = "."/><!-- 元の文字列 -->
                            <xsl:with-param name="replace-before" select="'&#xA;'"/><!-- 置換前の文字列 -->
                            <xsl:with-param name="replace-after"><br /></xsl:with-param><!-- 置換後の要素 -->
                        </xsl:call-template>
                    </td>
                    </xsl:for-each>
                </tr>
                </xsl:for-each>
            </tbody>
        </table>
    </body>    
</html>
    </xsl:template>

    <!-- 文字列置換用template -->
    <xsl:template name="replace-text">
        <xsl:param name="base-text"/><!-- 元の文字列 -->
        <xsl:param name="replace-before"/><!-- 置換前の文字列 -->
        <xsl:param name="replace-after"/><!-- 置換後の要素 -->
        <xsl:choose>
            <xsl:when test="contains($base-text, $replace-before)">
                <!-- 置換前の文字列がある場合 -->
                <xsl:value-of select="substring-before($base-text, $replace-before)"/>
                <xsl:copy-of select="$replace-after"/>

                <!-- 上の書き方では最初の1箇所しか置換できないため、再帰呼び出しで残りも置換する -->
                <xsl:call-template name="replace-text">
                    <xsl:with-param name="base-text"        select="substring-after($base-text, $replace-before)"/>
                    <xsl:with-param name="replace-before"   select="$replace-before"/>
                    <xsl:with-param name="replace-after"    select="$replace-after"/>
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="$base-text"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>
```

### XML宣言

```xml
<?xml version="1.0" encoding="UTF-8"?>
```

この箇所は基本的に変更する必要は無い。
XSLファイルをUTF-8以外の符号化方式で保存する場合は`encoding`を適宜変更すること。

## [xsl:stylesheet](https://www.w3.org/TR/xslt-10/#stylesheet-element)

```xml:xsl:stylesheet
<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0"

    xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882"
    xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
    xmlns:rs="urn:schemas-microsoft-com:rowset"
    xmlns:z="#RowsetSchema"

    xmlns:my="MSXMLのXSLTでcdata-section-elementsを利用するための名前空間定義(任意の文字列で可)"
    exclude-result-prefixes="s dt rs z my"
>
```
---

```xml:最低限の定義
<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0"
>
```
XSLとしての定義であれば、上記の部分だけで問題無い。
この記事では、Recordsetを対象としたり、CDATAセクションの定義のため、残りの部分を追加している。
なお`xmlns:xsl="http://www.w3.org/1999/XSL/Transform"`のURLの部分は、他のXSLでも同じのため、Windowsフォルダなどを適当に`*.xsl`で探せば同じものが見つかるはず。

---


```xml:名前空間の定義
    xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882"
    xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
    xmlns:rs="urn:schemas-microsoft-com:rowset"
    xmlns:z="#RowsetSchema"

    xmlns:my="MSXMLのXSLTでcdata-section-elementsを利用するための名前空間定義(任意の文字列で可)"
```

`xmlns:`の箇所はXML名前空間の定義となる。
`my`以外はRecordsetのXMLの1行目にあったものと同じ物となる。
Recordsetの構造を指定するときにあった方が便利なため、ここで定義している。

```xml:RecordsetのXMLの1行目
<xml xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882" xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882" xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema">
```

しかし、既定の設定ではこれらの名前空間の情報がそのまま結果のHTMLにも書き込まれてしまう。
ほとんどの名前空間の情報は結果のHTMLでは不要なため、`exclude-result-prefixes`でRecordsetの名前空間の情報が出力されないようにしている。

```html:exclude-result-prefixesを指定しなかった場合のファイル冒頭
<html lang="ja" xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882" xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882" xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema">`
```

### [xsl:output](https://www.w3.org/TR/xslt-10/#output)

```xml:xsl:output
<xsl:output 
    method="xml"
    indent="yes"
    omit-xml-declaration="yes"
    encoding="utf-8"
    cdata-section-elements="my:cdata"
    media-type="text/html"
/>
```

`method="xml"`としてXMLへの変換を指示しているが、XSLT的にはこの箇所は`method="html"`と指示することもできるようになっている。
しかし、`method="html"`と指示すると整形式のXMLとはならず、個人的に扱いにくかった。
そのため、`method="xml"`を明示し、整形式のXMLとして出力されるようにしている。

#### (参考)`method="html"`を指示した場合の結果

1. いくつかの終了タグを省略できるタグについて、終了タグが省略される(=整形式のXMLではなくなる)
1. `head`タグ直後に以下が挿入される

```html:headタグ直後に挿入されるタグ
<META http-equiv="Content-Type" content="text/html">
```



### HTMLファイル冒頭

```xml:HTMLファイル冒頭
    <xsl:template match="/">
<!-- <!DOCTYPE html>の出力用。-->
<xsl:text disable-output-escaping="yes">&lt;!DOCTYPE html&gt;&#10;</xsl:text>
<html lang="ja">
    <head>
        <meta charset="utf-8"/>
        <title>Recordset XML to HTML</title>
        <!-- HTMLファイル内にCSS 定義を含める(cdata-section-elementsのサンプル) -->
        <style text="text/stylesheet">/*<my:cdata><![CDATA[*/
table, th, td {
    border : solid thin; 
}
        /*]]></my:cdata>*/</style>
    </head>
    <body>
```

`<xsl:template match="/">`により、ここから`</xsl:template>`で閉じられるまでの範囲がHTMLとして出力される範囲となる。

基本的に`<xsl:`で始まっていないタグはそのまま出力される。

`style`タグ内の`my:cdata`タグは、[xsl:output](#xsloutput)の`cdata-section-elements="my:cdata"`で設定しているタグのため、結果出力時に内部の`<![CDATA[ ]>`が維持される。
`cdata-section-elements`を設定していない場合は`<![CDATA[ ]>`は結果に含まれない。

### 表見出し作成

```xml:表見出し作成
<table>
    <!-- 表の見出しとしてRecordsetのフィールド名を出力 -->
    <thead>
        <tr>
            <xsl:for-each select="xml/s:Schema/s:ElementType/s:AttributeType">
            <th>
                <!--
                    Recordsetのフィールド名に日本語などが含まれる場合、
                    @nameはc1などの表記となり、実際の名前は@rs:nameで定義される。
                    そのため、@rs:nameがある場合は@rs:nameを出力し、そうでない場合は@nameを出力している。
                -->
                <xsl:choose>
                    <xsl:when test="@rs:name">
                        <xsl:value-of select="@rs:name" />
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:value-of select="@name" />
                    </xsl:otherwise>
                </xsl:choose>
            </th>
            </xsl:for-each>
        </tr>
    </thead>
```

この箇所で、表の見出し部分を作成している。

[xsl:choose](https://www.w3.org/TR/xslt-10/#section-Conditional-Processing-with-xsl:choose)を使って[RecordsetのXML](#recordsetのxml)から列名に相当する情報を取得している。

### 表の中身作成

```xml:表の中身作成
            <tbody>
                <xsl:for-each select="xml/rs:data//z:row">
                <tr>
                    <xsl:for-each select="@*">
                    <td>
                        <!-- LF(&#xA;=改行)があった場合、brタグに置き換えるテンプレートを呼び出す。 -->
                        <xsl:call-template name="replace-text">
                            <xsl:with-param name="base-text" select = "."/><!-- 元の文字列 -->
                            <xsl:with-param name="replace-before" select="'&#xA;'"/><!-- 置換前の文字列 -->
                            <xsl:with-param name="replace-after"><br /></xsl:with-param><!-- 置換後の要素 -->
                        </xsl:call-template>
                    </td>
                    </xsl:for-each>
                </tr>
                </xsl:for-each>
            </tbody>
        </table>
    </body>    
</html>
    </xsl:template>
```

この箇所で表の中身の作成を行っている。

`<xsl:for-each select="xml/rs:data//z:row">`により、各`z:row`要素について`tr`タグを生成している。

各セルの値は後述の[文字列置換テンプレート](#文字列置換テンプレート)により、LF改行(`"'&#xA;'"`)を`br`タグに変換して出力をしている。

### 文字列置換テンプレート

```xml:文字列置換テンプレート
    <!-- 文字列置換用template -->
    <xsl:template name="replace-text">
        <xsl:param name="base-text"/><!-- 元の文字列 -->
        <xsl:param name="replace-before"/><!-- 置換前の文字列 -->
        <xsl:param name="replace-after"/><!-- 置換後の要素 -->
        <xsl:choose>
            <xsl:when test="contains($base-text, $replace-before)">
                <!-- 置換前の文字列がある場合 -->
                <xsl:value-of select="substring-before($base-text, $replace-before)"/>
                <xsl:copy-of select="$replace-after"/>

                <!-- 上の書き方では最初の1箇所しか置換できないため、再帰呼び出しで残りも置換する -->
                <xsl:call-template name="replace-text">
                    <xsl:with-param name="base-text"        select="substring-after($base-text, $replace-before)"/>
                    <xsl:with-param name="replace-before"   select="$replace-before"/>
                    <xsl:with-param name="replace-after"    select="$replace-after"/>
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="$base-text"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>
```

この[xsl:template](https://www.w3.org/TR/xslt-10/#element-template)は、RecordsetをHTMLに変換するだけであれば不要だが、自分の用途ではLFで改行された文字列を扱いたいことがあったため、作成したものとなる。

[HTMLファイル冒頭](#htmlファイル冒頭)では`match="/"`を指定していたが、ここでは`name="replace-text`で`name`属性を定義している。
`name`属性を指定することで[xsl:call-template](https://www.w3.org/TR/xslt-10/#element-call-template)により再利用が可能となっている。

このテンプレートは[表の中身作成](#表の中身作成)から呼び出しを行っており、以下のような処理を行う。

1. `base-text`内に`replace-before`の文字列が存在するか確認
1. 存在する場合、`replace-before`の前後で分割し、`replace-after`を間に挟む
1. `replace-before`の後ろ側を`base-text`として1.に戻る(再帰呼び出し)
1. `replace-before`が無くなれば終了

## VBAによるXSLT変換

```vb:XSLT変換処理サンプル
Public Sub Sample_Transform()
    'Excel の VBA で、MSXML2(=Microsoft XML, v6.0)を参照設定し、
    'ブックのあるフォルダ(`rootDir`)各種ファイルを保存している前提の記述。
    
    '`rootDir`には以下のファイルがあること(result.html はこのプロシージャにより作成される)。
    'Recordset.xml  :Recordsetの中身。
    'stylesheet.xsl :XSLファイル。
    'result.html    :出力ファイル。生成されたHTMLファイル。
    
    '各種ファイルの保存されているフォルダ
    Dim rootDir As String
    rootDir = ThisWorkbook.Path
    
    'Recordset の中身を XML 文書として読み込み。
    Dim basDoc As MSXML2.DOMDocument60
    Set basDoc = VBA.Interaction.CreateObject("MSXML2.DOMDocument.6.0")
    basDoc.Load rootDir & "\Recordset.xml"
    
    'スタイルシート(XSL ファイル)を XML 文書として読み込み。
    '公式リファレンスによるとスタイルシートは MSXML2.FreeThreadedDOMDocument.6.0 とすることになっている。
        'https://learn.microsoft.com/ja-jp/previous-versions/windows/desktop/ms762799(v=vs.85)
        '簡単に試した限り MSXML2.DOMDocument.6.0 でもエラーになることはない。
    Dim stSht As MSXML2.FreeThreadedDOMDocument60
    Set stSht = VBA.Interaction.CreateObject("MSXML2.FreeThreadedDOMDocument.6.0")
    stSht.Load rootDir & "\stylesheet.xsl"
    
    'basDoc にスタイルシートを適用した XML 文書を取得する。
        '結果が整形式の XML ではない場合は ADODB.Stream や文字列として返すようにするのが無難かも？
    Dim resultDoc As MSXML2.DOMDocument60
    Set resultDoc = TransformXML(basDoc, stSht)
    
    '結果の XML 文書を保存。
    resultDoc.Save rootDir & "\result.html"
End Sub

Public Function TransformXML( _
        ByVal inBaseDocument As MSXML2.DOMDocument60, _
        ByVal inStylesheet As MSXML2.FreeThreadedDOMDocument60 _
    ) As MSXML2.DOMDocument60
'inBaseDocument に inStylesheet を適用した XML 文書を返す処理。
'inBaseDocument :元となるXML文書。
'inStylesheet:  :変換に使用するスタイルシート。
'return         :変換後のXML文書。
240505
    'xsl:include などで他の .xsl などを参照する場合は True にする必要がある。
        'https://www.w3.org/TR/xslt-10/#element-include
    'inStylesheet.resolveExternals = True
    
    Dim tmpl As MSXML2.XSLTemplate60
    Set tmpl = VBA.Interaction.CreateObject("MSXML2.XSLTemplate.6.0")
    Set tmpl.stylesheet = inStylesheet
    
    Dim p As MSXML2.IXSLProcessor
    Set p = tmpl.createProcessor()
    
    '結果を出力するXML文書。 p.output に設定する。
    Dim destDoc As MSXML2.DOMDocument60
    Set destDoc = VBA.Interaction.CreateObject("MSXML2.DOMDocument.6.0")
    
    '<!DOCTYPE html>の箇所で
    'destDoc.parseError.reason = "DTD は禁止されています。"
    'のエラーとなってしまうため、DTDを許可させる。
        'https://learn.microsoft.com/ja-jp/previous-versions/windows/desktop/ms766391(v=vs.85)
    destDoc.SetProperty "ProhibitDTD", False
    
    'DTDを元にValidationをしようとしてしまうため、検証を防ぐ。
    destDoc.validateOnParse = False
    
    '入力ファイル・結果の出力先を指定して、変換。
    p.input = inBaseDocument
    p.output = destDoc
    p.transform
    
    Set TransformXML = destDoc
Exit Function
'IXSLProcessor.output 備考。
    'p.output に何も設定しなかった場合、 output プロパティは変換結果の文字列を返す。
    '1回しか参照出来ないため、変数に取得しておいた方が無難(ローカルウィンドウで参照するだけで消えてしまう)。
    
    'p.output には ADODB.Stream も指定可能。
    '変換後が整形式のXMLではない場合はこちらを使うのがよさそう。
    Dim sr As ADODB.Stream
    Set sr = VBA.Interaction.CreateObject("ADODB.Stream")
    sr.Open
    sr.Type = adTypeText
    sr.Charset = "utf-8" 'xsl:output@encoding と設定値を合わせること(合わせない場合は文字化け)。
    p.output = sr
    p.transform
    sr.Position = 0
End Function
```

ExcelのVBEで`ADODB`&`MSXML2`の参照設定を行い、ブックのあるフォルダに以下のファイルを保存している前提の処理となる。

|ファイル名|内容|
|:-|:-|
|Recordset.xml|[Recordsetの中身](#recordsetのxml)|
|stylesheet.xsl|[XSLファイル](#xslファイルの中身)|

基本的な処理の流れはリファレンスから大きく変えてはいないが、
以下の箇所はこの記事の方法でHTMLの表を出力するうえで重要な箇所となる。

```vb:TransformXML抜粋
    '結果を出力するXML文書。 p.output に設定する。
    Dim destDoc As MSXML2.DOMDocument60
    Set destDoc = VBA.Interaction.CreateObject("MSXML2.DOMDocument.6.0")
    
    '<!DOCTYPE html>の箇所で
    'destDoc.parseError.reason = "DTD は禁止されています。"
    'のエラーとなってしまうため、DTDを許可させる。
        'https://learn.microsoft.com/ja-jp/previous-versions/windows/desktop/ms766391(v=vs.85)
    destDoc.SetProperty "ProhibitDTD", False
    
    'DTDを元にValidationをしようとしてしまうため、検証を防ぐ。
    destDoc.validateOnParse = False
    
    '入力ファイル・結果の出力先を指定して、変換。
    p.input = inBaseDocument
    p.output = destDoc
```

まず、[xsl:output](#xsloutput)にも記載したように、結果のHTMLは整形式のXMLとしても扱える形式としている。
そのため、結果が整形式のXMLであることを明示するために`IXSLProcessor.output`には`MSXML2.DOMDocument.6.0`を指定している。

また`MSXML2.DOMDocument.6.0`の初期設定ではDTDを禁止する設定となっており、結果に`<!DOCTYPE html>`が含まれると解析エラーとなってしまう。
そのため、[setProperty](https://learn.microsoft.com/ja-jp/previous-versions/windows/desktop/ms760290(v=vs.85))でDTDを許可するように設定している。

DTDを許可した場合、`<!DOCTYPE html>`の記述では要素の定義がされていない状態のため、XMLを検証するとエラーとなってしまう。
今回の用途では検証は不要なため`validateOnParse = False`で検証を省略させている。

## 結果のHTMLファイル

```html
<!DOCTYPE html>
<html lang="ja">
	<head>
		<meta charset="utf-8"/>
		<title>Recordset XML to HTML</title>
		<style text="text/stylesheet">/*<my:cdata xmlns:my="MSXMLのXSLTでcdata-section-elementsを利用するための名前空間定義(任意の文字列で可)"><![CDATA[*/
table, th, td {
    border : solid thin; 
}
        /*]]></my:cdata>*/</style>
	</head>
	<body>
		<table>
			<thead>
				<tr>
					<th>Strings</th>
					<th>日付</th>
				</tr>
			</thead>
			<tbody>
				<tr>
					<td>Hoge<br/>Fuga</td>
					<td>2024-04-07T00:00:00</td>
				</tr>
				<tr>
					<td>Piyo</td>
					<td>2024-01-23T12:34:56</td>
				</tr>
			</tbody>
		</table>
	</body>
</html>
```

## 参考:PowerShellの場合

```powershell
using namespace System.Xml;
using namespace System.Xml.Xsl;

[XslCompiledTransform]$trans = [XslCompiledTransform]::new();
$trans.Load("$PSScriptRoot\stylesheet.xsl");
$trans.Transform(
    "$PSScriptRoot\Recordset.xml",
    "$PSScriptRoot\output.html"
);
```

## 参考ページ

- XSLT1.0のリファレンス

https://www.w3.org/TR/xslt-10/

- MSXML(VBAのXML操作ライブラリ)についてのリファレンス

https://learn.microsoft.com/ja-jp/previous-versions/windows/desktop/ms763742(v=vs.85)

- MSXMLのリファレンスの中で特に重要と思われるページ

https://learn.microsoft.com/ja-jp/previous-versions/windows/desktop/ms762799(v=vs.85)

https://learn.microsoft.com/ja-jp/previous-versions/windows/desktop/ms766391(v=vs.85)

- Microsoft公式の実装ではXSLT1.0までしか準拠していないことの確認。

https://stackoverflow.com/questions/1525299/xpath-and-xslt-2-0-for-net