XBRLインスタンスファイル解析

ここから実際にEXCEL VBAでの実装の説明に入ります。

ユーザ定義型

まず、ワークシート関数を実装する前に、XBRLインスタンスファイルをユーザ定義型の変数に読み込む処理を実装していきます。
最初に必要なユーザ定義型を3つ定義します。

'コンテキスト
Public Type TContext
    'コンテキストID
    ContextID As String
    '開始日
    StartDate As Variant
    '終了日
    EndDate As Variant
    '基準日
    Instant As Variant
    'シナリオ
    Scenario As Variant
    'NonConsolidateシナリオかどうか
    ScenarioNonConsolidate As Boolean
End Type

'ファクト
Public Type TFact
    '要素名
    ElementName As String
    '名前空間
    ElementNamespace As String
    'コンテキストID
    ContextID As String
    '値
    Value As Variant
    '単位
    Unit As Variant
    'Nil属性
    Nil As Boolean
    '精度(decimals属性)
    Decimals As Variant
End Type

'XBRLインスタンス
Public Type TInstance
    'XBRLインスタンスのファイルパス
    FilePath As String
    'コンテキスト
    Contexts() As TContext
    'ファクト
    Facts() As TFact
End Type

TContext型の開始日・終了日・基準日を(Date型にせず)Variant型にしたのは、VBAではDate型の初期値の関係で未設定の場合の扱いが面倒になるからです。値を設定するさいに、Date型の値を設定し、未設定の状態では初期値のEmptyとなるようにします。
TContext型のScenarioNonConsolidateは、シナリオにNonConsolidate要素が含まれているかを示します。EDINETのXBRLインスタンスにおいては、コンテキストのシナリオにNonConsolidate要素が含まれているということは、非連結(単独)のコンテキストであることを示します。

TFact型の値は、要素によって、文字列だけでなく日付・真偽・金額などが設定されるため、Variant型にしています。XBRLインスタンスから読み取った文字列の形式により、適当に型変換して設定します。

TInstance型は、XBRLインスタンスの情報を保持します。インスタンスには、コンテキストとファクト以外にも、参照しているスキーマのリファレンスや、単位やフットノートなどが記述されていますが、このツールでは意図的に無視しています。コンテキストとファクトは動的配列で保持します。

XMLファイルの読み込み

XBRLインスタンスはXMLで記述されています。本ツールでは、Microsoft.XMLDOMを使ってXMLを処理することにします。

'パースしたXBRLインスタンスを保持しておく領域
Private parsedInstanceFile(1024) As TInstance

'指定されたパスのインスタンスファイルを読み込んでパースし、
'XBRLインスタンス型にして変換する
Function ParseInstanceFile(ByVal path As String) As TInstance

    'モジュールレベル変数に保持されているかチェックし、
    '保持されている場合にはそれを返す
    Dim i As Integer
    
    For i = 0 To UBound(parsedInstanceFile)
        If (parsedInstanceFile(i).FilePath = path) Then
            ParseInstanceFile = parsedInstanceFile(i)
            Exit Function
        End If
        'FilePathが""のものがあったら、その先はないのでExitする。
        If (parsedInstanceFile(i).FilePath = "") Then
            Exit For
        End If
    Next

    'XMLオブジェクト(インスタンスファイルをセットする)
    Dim xml As Object
    'パースしたインスタンスを設定する
    Dim newInstance As TInstance
        
    'XMLファイル(インスタンスファイル)をロードする
    Set xml = CreateObject("Microsoft.XMLDom")
    '同期処理にする(ロードが完了するまで処理を待つ)
    xml.async = False
    'ロードする
    xml.Load path
    'インスタンスのメンバーを設定
    newInstance.FilePath = path
    newInstance.Contexts = GetContexts(xml)
    newInstance.Facts = GetFacts(xml)
    
    'モジュールレベル変数に保管しておく
    If (i <= UBound(parsedInstanceFile)) Then
        parsedInstanceFile(i) = newInstance
    End If
    
    Set xml = Nothing
    ParseInstanceFile = newInstance
    
End Function

ParseInstanceFile関数は、引数でXBRLインスタンスのパスを受け取り、そのパスのファイルをロードして解析(パース)して、TInstance型に格納します。
XMLをロードして解析する処理は重くなるため、一度解析したらモジュールレベルの変数に保存し、同じパスのファイルは何度も解析しないようにします。なお、保存する配列の長さは1025(添字0~1024まで)となっていますが、この長さに深い意味はありません。

この関数のキモは、TInstance型の変数 newInstance のメンバーContextsとFactsに値を設定するところですが、それはそれぞれ下請け関数のGetContexts/GetFacts関数で行います。

コンテキストの取得

XBRLインスタンスをロードしたXMLオブジェクトからコンテキスト(の配列)を取得します。

'インスタンスファイルのXMLオブジェクトから、コンテキストを取得する
Function GetContexts(ByVal xml As Object) As TContext()

    Dim nodeContexts As Collection
    Dim result() As TContext
    Dim idxNode As Object
    Dim i As Integer
    
    Set nodeContexts = myGetElementsByTagNameNS(xml, NAMESPACE_XBRLI, "context")
    
    ReDim result(0 To nodeContexts.Count - 1)
    i = 0
    For Each idxNode In nodeContexts
        result(i) = ToContext(idxNode)
        i = i + 1
    Next
    GetContexts = result

End Function

'XMLノードからTContextに変換する
Function ToContext(ByVal xml As Object) As TContext
    Dim result As TContext
    Dim element As Object
    Dim dateElements As Collection
    Dim att As Object
    Dim scenarioElements As Collection
    
    'コンテキストIDの設定
    For Each att In xml.Attributes()
        If (att.baseName = "id") Then
            result.ContextID = att.Text
            Exit For
        End If
    Next
    
    '開始日の設定
    Set dateElements = myGetElementsByTagNameNS(xml, NAMESPACE_XBRLI, "startDate")
    If (dateElements.Count > 0) Then
        result.StartDate = CDate(dateElements(1).Text)
    End If
    
    '終了日の設定
    Set dateElements = myGetElementsByTagNameNS(xml, NAMESPACE_XBRLI, "endDate")
    If (dateElements.Count > 0) Then
        result.EndDate = CDate(dateElements(1).Text)
    End If
    
    '基準日の設定
    Set dateElements = myGetElementsByTagNameNS(xml, NAMESPACE_XBRLI, "instant")
    If (dateElements.Count > 0) Then
        result.Instant = CDate(dateElements(1).Text)
    End If
        
    '非連結メンバー(NonConsolidateMember)かどうかの設定
    Set scenarioElements = myGetElementsByTagNameNS(xml, NAMESPACE_XBRLI, "scenario")
    If (scenarioElements.Count > 0) Then
        result.Scenario = scenarioElements(1).Text
        For Each element In scenarioElements(1).getElementsByTagName("*")
            If (element.Text Like "*NonConsolidatedMember*") Then
                result.ScenarioNonConsolidate = True
                Exit For
            End If
        Next
    End If
    
    ToContext = result
    
End Function

GetContexts関数は、XMLオブジェクトから、名前空間「http://www.xbrl.org/2003/instance」、名称「context」のノードを取得します。この各ノードが1つのTContext型の変数に該当します。

contextノードは、典型的には以下のようなXMLノードです。

<xbrli:context id="CurrentYearDuration_NonConsolidatedMember">
    <xbrli:entity>
        <xbrli:identifier scheme="http://disclosure.edinet-fsa.go.jp">E32364-000</xbrli:identifier>
    </xbrli:entity>
    <xbrli:period>
        <xbrli:startDate>2016-07-01</xbrli:startDate>
        <xbrli:endDate>2017-06-30</xbrli:endDate>
    </xbrli:period>
    <xbrli:scenario>
        <xbrldi:explicitMember dimension="jppfs_cor:ConsolidatedOrNonConsolidatedAxis">jppfs_cor:NonConsolidatedMember</xbrldi:explicitMember>
    </xbrli:scenario>
</xbrli:context>

ToContext関数は、各contextノードからTContext型の変数に変換します。id属性の値をContextIDに、startDateタグの値をStartDateに、endDateタグの値をEndDate、Instantタグの値をInstant、scenarioタグの内容をScenarioにそれぞれ設定していきます。scenarioタグの中にNonConsolidatedMember(非連結メンバー)が含まれるかチェックし、含まれる場合にはScenarioNonConsolidateの値をTrue(非連結)にしています。

ファクトの取得

XBRLインスタンスをロードしたXMLオブジェクトからファクト(の配列)を取得します。

'インスタンスファイルのXMLオブジェクトから、ファクトを取得する
Function GetFacts(ByVal xml As Object) As TFact()

    Dim xbrlNodes As Object
    Dim result() As TFact
    Dim element As Object
    Dim nodeFacts As Collection
    Dim idx As Integer
    
    Set nodeFacts = New Collection
    'xbrlのノード(ルートノード)を取得する
    Set xbrlNodes = myGetElementsByTagNameNS(xml, NAMESPACE_XBRLI, "xbrl")
    If (xbrlNodes.Count > 0) Then
        'ルートノードの子ノードには、ファクトのほか、コンテキストやフットノートリンクやSchemaRefなどが定義される。
        'それらの名前空間は決まっているため、それ以外の名前空間に属したものをファクトとする。
        For Each element In xbrlNodes(1).ChildNodes()
            If (element.namespaceURI <> NAMESPACE_LINKBASE And _
                element.namespaceURI <> NAMESPACE_XBRLI) Then
                nodeFacts.Add element
            End If
        Next
    End If
    
    ReDim result(0 To nodeFacts.Count - 1)
    idx = 0
    For Each element In nodeFacts
        result(idx) = ToFact(element)
        idx = idx + 1
    Next
    GetFacts = result
    
End Function

'XMLノードからTFactに変換する
Function ToFact(ByVal xml As Object) As TFact
    Dim result As TFact
    Dim att As Object
    
    result.ElementName = xml.baseName
    result.ElementNamespace = xml.namespaceURI
    For Each att In xml.Attributes()
        If (att.baseName = "contextRef") Then
            result.ContextID = att.Text
        ElseIf (att.baseName = "unitRef") Then
            result.Unit = att.Text
        ElseIf (att.baseName = "decimals") Then
            result.Decimals = att.Text
        ElseIf (att.namespaceURI = NAMESPACE_XMLSI And att.baseName = "nil") Then
            result.Nil = CBool(att.Text)
        End If
    Next
    result.Value = IIf(result.Nil, CVErr(xlErrNull), ToValue(xml.Text))
    
    ToFact = result
End Function

'TFactのValueに設定する値を、文字列の形式から適切な型に変換して返す
Private Function ToValue(ByVal str As String) As Variant

    If (IsNumeric(str)) Then
        ToValue = Val(str)
    ElseIf (UCase(str) = "TRUE" Or UCase(str) = "FALSE") Then
        ToValue = CBool(str)
    ElseIf (IsDate(str)) Then
        ToValue = CDate(str)
    Else
        ToValue = str
    End If
    
End Function

GetFacts関数は、XMLオブジェクトから、xbrlノード(ルートノード)の子ノードのうちFactを表すノードを取得します。各ノードが1つのTFact型の変数に該当します。

ToFact関数は、そのノードをTFactに変換します。contextRef属性、unitRef属性、decimals属性、nil属性の値をそれぞれメンバー変数に格納し、ノードのTextをValueに設定します。Value設定するときに、ToValue関数で文字列のフォーマットからデータ型を判定し、キャストしています。


前のページ:ワークシート関数の設計
次のページ:コンテキスト取得関数