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でのグラフ化などに使います。
以上です。