ラベルファイルを読み込むコード例【Python】

EDINET決算分析システム

XBRLのラベルファイルを読み込むコード例です。

EDINET XBRL仕様書で、名称リンクと呼ばれているあたりの内容です。

ラベルファイルを見ると分かるのですが、たいていの場合、ひとつの勘定科目に対して複数のラベルが定義されています。

利益が黒字の時に使うラベル、赤字の時に使うラベル、合計値のところに使うラベル、みたいな感じです。

そのあたりの種類は、ラベルタグのロール属性で定義されていました。

スタンダード ラベル ロール アトリビュート バリュー
Standard label role attribute values.
http://www.xbrl.org/Specification/XBRL-2.1/REC-2003-12-31/XBRL-2.1-REC-2003-12-31+corrected-errata-2013-02-20.html#Standard-label-role-attribute-values

とりあえず、決算データベースでは 'http://www.xbrl.org/2003/role/label' のラベルを選んで表示しています。

それがなければ、ほかに存在するラベルを適当に選んで表示しています。

以下、ラベルファイルの読み込みコード例です。

ラベルファイルを解析するクラス

ラベルファイル用の名前空間を定義して、検索したいタグ名・属性名を定義します。ラベルの読み込みでは、以下のタグから勘定科目とラベルの対応を組み立てていきます。

  • ロケータータグ <loc />
  • ラベルタグ <label>ラベル名</label>
  • ラベルアークタグ <labelArc />

名前空間を定義する

以下の名前空間を使います。接頭辞 'xml' の名前空間は、"ja""en"といったlang属性の設定で使われています。

"""xbrl_namespace.py"""
NS_LAB = {
    'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
    'xlink': 'http://www.w3.org/1999/xlink',
    'link': 'http://www.xbrl.org/2003/linkbase',
    'xml': 'http://www.w3.org/XML/1998/namespace',
    }

ラベルファイルを読み込むところ

EDINET XBRLのラベルファイルは、日本語(ja)と英語(en)の2種類が用意されています。

これらの言語は、「ファイル名」と「ラベルタグのlang属性」の2か所から判定できます。ファイル名は正規表現で判定しています。

あと、2011年7月25日に「追加タクソノミ」というものが公表されているのですが、これに関する読み込みはスキップしています。

金融庁の説明を見るに、株主資本等変動計算書の「前期末残高」を「当期首残高」と表示するためのもの、ということで、決算データベースにはあまり影響なさそうでした。

この追加タクソノミには ad という文字列が含まれていますので、正規表現で判定してスキップしています。

(ad はたぶん add の略だと思います)

そのあたりのURIの定義は「企業別タクソノミ作成ガイドライン 追補」に載っていました。

"""xbrl_lab.py"""

from os.path import basename as os_basename
import re
from collections import OrderedDict
import xbrl_namespace
from xbrl_util import conv_relative_to_abs
from xbrl_util import get_etree_obj_from_file

RE_LANG_JA_MATCH = re.compile('(?:^jp.*?(?:-label(?:-cte)?|_lab)\.xml$|^ifrs.*?-label-ja\.xml$|^lab_ifrs-ja.*?\.xml$)').match
RE_LANG_EN_MATCH = re.compile('(?:^jp.*?(?:-label-en(?:-cte)?|_lab-en)\.xml$|^ifrs.*?-label\.xml$|^lab_ifrs-en.*?\.xml$)').match
RE_TAXONOMY_AD_MATCH = re.compile('^jpfr-t-[a-z]*-[0-9]{4}-[0-9]{2}-[0-9]{2}-ad-[0-9]{4}-[0-9]{2}-[0-9]{2}-label[^.]*\.xml$').match
class Parser:
    """labファイル解析クラス"""
    def __init__(self, file, file_data=None):
        self.file = file

        # ファイル名から言語を判定
        # 企業別タクソノミ作成ガイドライン 企業別タクソノミのファイル仕様 ファイル名 20130331
        # 提出者別タクソノミ作成ガイドライン ファイル名 名称リンクの命名規約 20180228
        if RE_LANG_JA_MATCH(os_basename(self.file)):
            self.lang = 'ja'
        elif RE_LANG_EN_MATCH(os_basename(self.file)):
            self.lang = 'en'
        else:
            # 想定外の言語
            self.lang = self.file.rsplit('-', maxsplit=1)[1].replace('.xml', '')
            raise

        # XMLファイルを読み込む
        self.root = get_etree_obj_from_file(self.file, file_data)

        # 名前空間(NameSpace)の定義を取得
        ns_def = xbrl_namespace.NS_LAB

        # タグ名/属性名定義
        self.link_label_link = '{%s}labelLink' % ns_def['link']
        self.link_loc = '{%s}loc' % ns_def['link']
        self.link_label = '{%s}label' % ns_def['link']
        self.link_label_arc = '{%s}labelArc' % ns_def['link']
        self.xlink_href = '{%s}href' % ns_def['xlink']
        self.xlink_label = '{%s}label' % ns_def['xlink']
        self.xml_lang = '{%s}lang' % ns_def['xml']
        self.xlink_role = '{%s}role' % ns_def['xlink']
        self.xlink_arcrole = '{%s}arcrole' % ns_def['xlink']
        self.xlink_from = '{%s}from' % ns_def['xlink']
        self.xlink_to = '{%s}to' % ns_def['xlink']

        # ファイル名から追加タクソノミか否かを判定
        if RE_TAXONOMY_AD_MATCH(os_basename(self.file)):
            # 追加タクソノミ
            self.labels = None
        else:
            # ラベル辞書を取得
            self.labels = self.get_labels()

        # 変数削除
        del self.root
        return

 loc, label, labelArcタグからラベル辞書を作る

「ロケータータグ」と「ラベルタグ」から、ラベル辞書を作ります。

「ロケータータグ」と「ラベルタグ」は、「ラベルアークタグのfrom属性とto属性」でつなげます。

このような書き方は、XMLの機能です。XBRLでは、それを利用してラベルなどを定義している形です。

以下は、XBRLのラベルリンクのページです。

The <labelLink> element (ラベルリンク エレメント)
http://www.xbrl.org/Specification/XBRL-2.1/REC-2003-12-31/XBRL-2.1-REC-2003-12-31+corrected-errata-2013-02-20.html#_5.2.2

とりあえず、「locタグのlabel属性とlabelArcのfrom属性」、「labelタグのlabel属性とlabelArcのto属性」が対応しているようでしたので、それを手掛かりに辞書を作って、つなげていきます。

locタグのhref属性には、ラベルに対応するスキーマファイルと勘定科目が入っています。

いずれもXBRLの勘定科目にラベル付けしていくときに使いますので、分解して取得しておきます。

このhref属性にある勘定科目の名前が、スキーマファイル(xsd)のエレメントタグのid属性に対応しています。

ところで、タグを見ていると、ロール(role)とかアーク(arc)といった言葉が出てきました。

ロールは役割といったイメージの言葉です。アークは指先からドアノブにパチッと走った線みたいなイメージでしょうか。それが2点間をつないでいく感じだと思っています。

以下がコード例です。

    def get_labels(self):
        """ラベル辞書取得"""
        debug_raised = False

        od_label_link = OrderedDict()
        for tag_label_link in self.root.findall('.//%s' % self.link_label_link):
            # locタグのlabel属性をキーにして辞書を作成
            od_loc = OrderedDict()
            for element in tag_label_link.findall('.//%s' % self.link_loc):
                key = element.get(self.xlink_label)
                assert key not in od_loc

                href = element.get(self.xlink_href).split('#', maxsplit=1)
                assert len(href) == 2

                od_loc.update({key: {
                    'file': conv_relative_to_abs(href[0], self.file),
                    'id': href[1],
                    'label': OrderedDict(),
                    }})

            # labelタグのlabel属性をキーにして辞書を作成
            od_label = OrderedDict()
            for element in tag_label_link.findall('.//%s' % self.link_label):

                # ファイル名から推定した言語とlabelタグの言語は一致するはず
                if element.get(self.xml_lang) != self.lang:
                    print('  %s\n  %s' % (element.get(self.xml_lang), self.lang))
                    assert element.get(self.xml_lang) == self.lang

                od_label.update({
                    element.get(self.xlink_label):{
                        'role': element.get(self.xlink_role),
                        'lang': element.get(self.xml_lang),
                        'text': element.text,
                        }})

            # labelArcタグのfrom属性とto属性を取得。
            # これらキーにして、loc辞書の各キーに対応するlabel辞書を入れる。
            for element in tag_label_link.findall('.//%s' % self.link_label_arc):
                xlink_from = element.get(self.xlink_from)
                xlink_to = element.get(self.xlink_to)

                if xlink_to in od_label:
                    od_loc[xlink_from]['label'].update({
                        od_label[xlink_to]['role']: {
                            'lang': od_label[xlink_to]['lang'],
                            'text': od_label[xlink_to]['text'],
                            }})
                else:
                    if not debug_raised:
                        print('  get_labels')
                        print('  not used key: %s' % xlink_to)
                        debug_raised = True

            # ラベルリンク辞書に追加
            od_label_link.update({
                tag_label_link.get(self.xlink_role): {
                    'loc': od_loc,
                }})
        return {self.lang: od_label_link}

それと、locタグのhref属性を絶対パスに変換している関数です。ラベルファイルのパスを使って、絶対パスにしています。

"""xbrl_util.py"""

from os.path import join as os_join
from os.path import abspath as os_abspath
from os.path import dirname as os_dirname
from urllib.parse import urljoin
import re

RE_RELATIVE_URL_MATCH = re.compile('^[.]{1,2}[\\\\/].*?$').match
RE_WEB_URL_MATCH = re.compile('^https?://.*?$').match
RE_FULL_PATH_MATCH = re.compile('^.*?[\\\\/].*?$').match
def conv_relative_to_abs(url, file):
    """相対パスを絶対パスに変換"""
    if RE_RELATIVE_URL_MATCH(url):
        if RE_WEB_URL_MATCH(file):
            return urljoin(file, url)
        return os_abspath(os_join(os_dirname(file), url))
    else:
        if RE_FULL_PATH_MATCH(url):
            return url

        if RE_WEB_URL_MATCH(file):
            return urljoin(file, url)
        return os_join(os_dirname(file), url)

実際にラベルファイルを読み込んでみる

ラベルファイルを指定して、ラベル辞書を作るテストです。

"""xbrl_lab.py"""

from os.path import basename as os_basename
import re
from collections import OrderedDict
import xbrl_namespace
from xbrl_util import conv_relative_to_abs
from xbrl_util import get_etree_obj_from_file

def main():
    """モジュールテスト"""
    file = r"*****\jpcrp030000-asr-001_E00000-000_2017-03-31_01_2017-06-29_lab.xml"
    obj = Parser(file)
    print('end')
    return

class Parser:
    """labファイル解析クラス"""
    (省略)

if __name__ == '__main__':
    main()

XBRL・スキーマ・ラベルでデータベース完成

これまでに作ったパーサーを使って、ラベルを追加するプログラムを書きます。

スキーマのエレメントタグの辞書にラベルを追加して、それを使ってXBRLにラベル付けをしていく感じです。エレメントタグのid属性とname属性を使って、XBRLとラベルをひも付けていきます。

実際には、スキーマファイルをたどったり、タクソノミをダウンロードしたり、パーサーの戻り値をキャッシュしたり、作るところが結構ありました。ラベル追加だけでも大仕事です。

で、作ってみた感じですが、やはり、日本語ラベルがあると見易かったです。勘定科目集約の助けにもなりそうでした。結果はpickleに保存して、matplotlibでのグラフ化などに使います。

以上です。

スポンサーリンク
シェアする(押すとSNS投稿用の『編集ページ』に移動します)
フォローする(RSSフィードに移動します)
スポンサーリンク
シラベルノート
タイトルとURLをコピーしました