前言:XBRL 的全球化與在地化挑戰
在數位時代,財務報告的標準化與互通性成為國際金融市場的重要基石。XBRL(eXtensible Business Reporting Language)作為一種基於 XML 的財務資訊交換標準,已被全球超過 50 個國家採用。然而,各國在導入 XBRL 時,都需面對如何在遵循國際標準的同時,適應本地會計準則與實務需求的挑戰。
台灣證券交易所自 2018 年起全面實施 iXBRL(Inline XBRL)格式財報申報,建立了基於國際財務報導準則(IFRS)但具有台灣特色的 TIFRS(Taiwan International Financial Reporting Standards)分類標準。對於開發財報處理系統的技術人員而言,深入理解 TIFRS 與國際標準的差異,是確保系統正確性與穩定性的關鍵。
XBRL 國際標準架構
在探討台灣特色之前,我們先了解 XBRL 的國際標準架構。
核心規格層次
XBRL 2.1 核心元素
XBRL 2.1 規範定義了基礎架構,所有國家的實作都必須遵循:
基本命名空間
| 前綴 | 命名空間 URI | 用途 |
|---|---|---|
| xbrli | http://www.xbrl.org/2003/instance | XBRL 實例文件 |
| link | http://www.xbrl.org/2003/linkbase | 連結基礎 |
| xlink | http://www.w3.org/1999/xlink | XML 連結 |
| xs | http://www.w3.org/2001/XMLSchema | XML Schema |
| iso4217 | http://www.xbrl.org/2003/iso4217 | 貨幣代碼 |
文件結構範例
<?xml version="1.0" encoding="UTF-8"?>
<xbrli:xbrl
xmlns:xbrli="http://www.xbrl.org/2003/instance"
xmlns:link="http://www.xbrl.org/2003/linkbase"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:iso4217="http://www.xbrl.org/2003/iso4217">
<!-- Schema 參照 -->
<link:schemaRef
xlink:type="simple"
xlink:href="http://example.com/taxonomy.xsd" />
<!-- Context 定義 -->
<xbrli:context id="Current_AsOf">
<xbrli:entity>
<xbrli:identifier scheme="http://example.com">Company001</xbrli:identifier>
</xbrli:entity>
<xbrli:period>
<xbrli:instant>2024-12-31</xbrli:instant>
</xbrli:period>
</xbrli:context>
<!-- Unit 定義 -->
<xbrli:unit id="USD">
<xbrli:measure>iso4217:USD</xbrli:measure>
</xbrli:unit>
<!-- Fact 數據 -->
<ifrs-full:Assets contextRef="Current_AsOf" unitRef="USD" decimals="-3">
1500000000
</ifrs-full:Assets>
</xbrli:xbrl>
國際主要分類標準
各標準特色比較
| 標準 | 基礎準則 | 元素數量 | 語言 | 特色 |
|---|---|---|---|---|
| IFRS | 國際會計準則 | ~5,000 | 英文為主 | 全球通用 |
| US-GAAP | 美國會計準則 | ~15,000 | 英文 | 最詳細完整 |
| JP-GAAP | 日本會計準則 | ~3,500 | 日文/英文 | 保守主義 |
| CN-GAAP | 中國會計準則 | ~4,000 | 簡體中文 | 趨同 IFRS |
| TIFRS | 等同 IFRS | ~5,500 | 繁體中文 | IFRS + 本土化 |
台灣 TIFRS 標準深度解析
命名空間體系
TIFRS 建立了獨立的命名空間體系,與國際 IFRS 並行但不相同。
國際 IFRS 命名空間結構
<!-- IFRS 標準命名空間 -->
xmlns:ifrs="http://xbrl.ifrs.org/taxonomy/2023-03-23/ifrs"
xmlns:ifrs-full="http://xbrl.ifrs.org/taxonomy/2023-03-23/ifrs-full"
台灣 TIFRS 命名空間結構
<!-- TIFRS 基礎命名空間 -->
xmlns:tifrs="http://www.xbrl.org/tw/fr/tifrs/2022-01-01"
<!-- TIFRS 分支命名空間 -->
xmlns:tifrs-bsta="http://www.xbrl.org/tw/fr/tifrs/bsta/2022-01-01" <!-- 資產負債表 -->
xmlns:tifrs-cor="http://www.xbrl.org/tw/fr/tifrs/cor/2022-01-01" <!-- 核心概念 -->
xmlns:tifrs-pfs="http://www.xbrl.org/tw/fr/tifrs/pfs/2022-01-01" <!-- 損益表 -->
xmlns:tifrs-cfs="http://www.xbrl.org/tw/fr/tifrs/cfs/2022-01-01" <!-- 現金流量表 -->
xmlns:tifrs-sce="http://www.xbrl.org/tw/fr/tifrs/sce/2022-01-01" <!-- 權益變動表 -->
命名空間架構比較
實體識別碼(Entity Identifier)差異
國際標準做法
<xbrli:entity>
<xbrli:identifier scheme="http://www.sec.gov/CIK">0000012345</xbrli:identifier>
</xbrli:entity>
台灣 TIFRS 做法
<xbrli:entity>
<xbrli:identifier scheme="公開資訊觀測站">2330</xbrli:identifier>
</xbrli:entity>
台灣使用證券代號(Stock Code)作為主要識別方式,與國際上常用的 LEI(Legal Entity Identifier)或 CIK(Central Index Key)不同。
會計科目對應與差異
雖然 TIFRS 基本上等同於 IFRS,但在具體科目命名和分類上存在差異。
資產負債表科目對照
台灣特有科目範例
<!-- 台灣特有:預收款項(在流動負債中) -->
<tifrs-bsta:AdvanceReceipts
contextRef="Current_AsOf"
unitRef="TWD"
decimals="-3">
1500000
</tifrs-bsta:AdvanceReceipts>
<!-- 台灣特有:存出保證金 -->
<tifrs-bsta:RefundableDeposits
contextRef="Current_AsOf"
unitRef="TWD"
decimals="-3">
850000
</tifrs-bsta:RefundableDeposits>
<!-- 台灣特有:遞延所得稅負債 - 非流動 -->
<tifrs-bsta:DeferredTaxLiabilitiesNoncurrent
contextRef="Current_AsOf"
unitRef="TWD"
decimals="-3">
2300000
</tifrs-bsta:DeferredTaxLiabilitiesNoncurrent>
語言標籤系統
TIFRS 最顯著的特色是完整的繁體中文標籤系統。
IFRS 標籤結構
<link:label
xlink:type="resource"
xlink:label="ifrs-full_Assets_label"
xml:lang="en">
Assets
</link:label>
TIFRS 雙語標籤結構
<!-- 繁體中文標籤 -->
<link:label
xlink:type="resource"
xlink:label="tifrs-bsta_Assets_label"
xml:lang="zh-TW">
資產總計
</link:label>
<!-- 英文標籤 -->
<link:label
xlink:type="resource"
xlink:label="tifrs-bsta_Assets_label"
xml:lang="en">
Assets
</link:label>
程式化提取標籤
def get_element_label(element, preferred_lang='zh-TW'):
"""
取得元素的標籤,優先使用指定語言
Args:
element: XBRL 元素
preferred_lang: 偏好語言('zh-TW' 或 'en')
Returns:
str: 元素標籤
"""
# 嘗試取得偏好語言標籤
labels = element.label(lang=preferred_lang, labelRole='http://www.xbrl.org/2003/role/label')
if labels:
return labels
# 降級到英文標籤
labels = element.label(lang='en', labelRole='http://www.xbrl.org/2003/role/label')
if labels:
return labels
# 最後使用元素本地名稱
return element.qname.localName
技術實作關鍵差異
文件結構完整範例
典型 TIFRS iXBRL 文件結構
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:xbrli="http://www.xbrl.org/2003/instance"
xmlns:link="http://www.xbrl.org/2003/linkbase"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:iso4217="http://www.xbrl.org/2003/iso4217"
xmlns:tifrs-bsta="http://www.xbrl.org/tw/fr/tifrs/bsta/2022-01-01"
xmlns:tifrs-cor="http://www.xbrl.org/tw/fr/tifrs/cor/2022-01-01">
<head>
<meta charset="UTF-8"/>
<title>台灣積體電路製造股份有限公司 - 合併資產負債表</title>
<!-- Schema 參照 -->
<link:schemaRef
xlink:type="simple"
xlink:href="http://www.xbrl.org/tw/fr/tifrs/2022-01-01/tifrs-2022-01-01.xsd"/>
</head>
<body>
<!-- Hidden XBRL Contexts -->
<div style="display:none">
<!-- 期間定義 -->
<xbrli:context id="CurrentYearEnd">
<xbrli:entity>
<xbrli:identifier scheme="公開資訊觀測站">2330</xbrli:identifier>
</xbrli:entity>
<xbrli:period>
<xbrli:instant>2024-12-31</xbrli:instant>
</xbrli:period>
</xbrli:context>
<xbrli:context id="CurrentYearDuration">
<xbrli:entity>
<xbrli:identifier scheme="公開資訊觀測站">2330</xbrli:identifier>
</xbrli:entity>
<xbrli:period>
<xbrli:startDate>2024-01-01</xbrli:startDate>
<xbrli:endDate>2024-12-31</xbrli:endDate>
</xbrli:period>
</xbrli:context>
<!-- 貨幣單位 -->
<xbrli:unit id="TWD">
<xbrli:measure>iso4217:TWD</xbrli:measure>
</xbrli:unit>
<xbrli:unit id="shares">
<xbrli:measure>xbrli:shares</xbrli:measure>
</xbrli:unit>
</div>
<!-- 可見的財務報表內容 -->
<table>
<tr>
<td>資產總計</td>
<td>
<ix:nonFraction
name="tifrs-bsta:Assets"
contextRef="CurrentYearEnd"
unitRef="TWD"
decimals="-3"
format="ixt:num-dot-decimal">
3,500,000,000
</ix:nonFraction>
</td>
</tr>
</table>
</body>
</html>
日期與期間處理
台灣財報期間標記方式
<!-- 第一季 -->
<xbrli:context id="2024Q1">
<xbrli:entity>
<xbrli:identifier scheme="公開資訊觀測站">2330</xbrli:identifier>
</xbrli:entity>
<xbrli:period>
<xbrli:startDate>2024-01-01</xbrli:startDate>
<xbrli:endDate>2024-03-31</xbrli:endDate>
</xbrli:period>
<xbrli:scenario>
<tifrs-cor:PeriodTypeAxis>
<tifrs-cor:FirstQuarter/>
</tifrs-cor:PeriodTypeAxis>
</xbrli:scenario>
</xbrli:context>
Python 期間處理工具
from datetime import datetime
from typing import Tuple
def parse_taiwan_period(context) -> Tuple[datetime, datetime, str]:
"""
解析台灣財報期間
Returns:
(開始日期, 結束日期, 期間類型)
"""
period = context.period
if period.isInstant:
date = period.instant
return date, date, "時點"
else:
start = period.startDate
end = period.endDate
# 判斷期間類型
months = (end.year - start.year) * 12 + (end.month - start.month)
if months == 3:
quarter = (end.month - 1) // 3 + 1
period_type = f"Q{quarter}"
elif months == 6:
period_type = "半年報"
elif months == 12:
period_type = "年報"
else:
period_type = f"{months}個月"
return start, end, period_type
# 使用範例
start, end, period_type = parse_taiwan_period(context)
print(f"期間:{start} 至 {end} ({period_type})")
股本計算特殊性
台灣上市櫃公司多採用每股面額 10 元的標準,這在計算流通股數時需要特別處理。
台灣股本計算邏輯
實作範例
def calculate_taiwan_shares(financial_data: dict) -> dict:
"""
計算台灣公司的股數相關指標
Args:
financial_data: 包含權益總額、實收資本額等資訊
Returns:
計算結果字典
"""
# 台灣標準面額
PAR_VALUE = 10.0
# 方法一:從權益總額計算
equity = financial_data.get('equity', 0)
shares_from_equity = equity / PAR_VALUE if equity else 0
# 方法二:從實收資本額計算(更準確)
paid_in_capital = financial_data.get('paid_in_capital', 0)
shares_from_capital = paid_in_capital / PAR_VALUE if paid_in_capital else 0
# 優先使用實收資本額計算
outstanding_shares = shares_from_capital if paid_in_capital else shares_from_equity
# 計算每股指標
net_income = financial_data.get('net_income', 0)
eps = net_income / outstanding_shares if outstanding_shares else 0
book_value_per_share = equity / outstanding_shares if outstanding_shares else 0
return {
'outstanding_shares': outstanding_shares,
'eps': eps,
'book_value_per_share': book_value_per_share,
'par_value': PAR_VALUE
}
# 使用範例:台積電
tsmc_data = {
'equity': 3_000_000_000_000, # 3 兆元
'paid_in_capital': 2_593_000_000_000, # 2.593 兆元
'net_income': 980_000_000_000 # 9800 億元
}
result = calculate_taiwan_shares(tsmc_data)
print(f"流通股數: {result['outstanding_shares']:,.0f} 股")
print(f"每股盈餘: {result['eps']:.2f} 元")
print(f"每股淨值: {result['book_value_per_share']:.2f} 元")
輸出
流通股數: 259,300,000,000 股
每股盈餘: 3.78 元
每股淨值: 11.57 元
貨幣單位與尺度處理
TIFRS 貨幣單位標記
<!-- 新台幣單位 -->
<xbrli:unit id="TWD">
<xbrli:measure>iso4217:TWD</xbrli:measure>
</xbrli:unit>
<!-- 外幣單位 -->
<xbrli:unit id="USD">
<xbrli:measure>iso4217:USD</xbrli:measure>
</xbrli:unit>
數值尺度標記
台灣財報常用「千元」、「百萬元」等尺度單位:
<!-- 使用 decimals 屬性表示精度 -->
<tifrs-bsta:Assets
contextRef="Current"
unitRef="TWD"
decimals="-3">
1500000
</tifrs-bsta:Assets>
<!-- 實際值:1500000 × 1000 = 1,500,000,000 元 -->
尺度轉換工具
def normalize_taiwan_amount(value: float, decimals: int) -> float:
"""
根據 decimals 屬性正規化金額
Args:
value: XBRL 文件中的數值
decimals: decimals 屬性值
Returns:
正規化後的金額(單位:元)
"""
if decimals is None:
return value
# decimals 為負數時,表示精度到多少位
# decimals="-3" 表示千元
# decimals="-6" 表示百萬元
if decimals < 0:
multiplier = 10 ** abs(decimals)
return value * multiplier
else:
return value
# 範例
value_in_doc = 1500000 # 文件中的值
decimals = -3 # 千元
actual_value = normalize_taiwan_amount(value_in_doc, decimals)
print(f"文件值: {value_in_doc:,}")
print(f"實際值: {actual_value:,.0f} 元")
print(f"十億元: {actual_value / 1_000_000_000:.2f}")
完整解析實作範例
使用 Arelle 解析 TIFRS 文件
Arelle 是最常用的開源 XBRL 處理器,以下展示如何用它解析台灣財報。
安裝 Arelle
# 使用 pip 安裝
pip install arelle-release
# 或從源碼安裝
git clone https://github.com/Arelle/Arelle.git
cd Arelle
python setup.py install
完整解析範例
from arelle import Cntlr
from arelle.ModelXbrl import ModelXbrl
from typing import Dict, List
import json
class TIFRSParser:
"""台灣 iXBRL 文件解析器"""
def __init__(self, xbrl_file: str):
"""
初始化解析器
Args:
xbrl_file: XBRL/iXBRL 檔案路徑
"""
self.controller = Cntlr.Cntlr()
self.model: ModelXbrl = self.controller.modelManager.load(xbrl_file)
def get_company_info(self) -> Dict:
"""提取公司基本資訊"""
company_info = {}
for context in self.model.contexts.values():
if context.entityIdentifier:
scheme = context.entityIdentifier[0]
identifier = context.entityIdentifier[1]
if scheme == "公開資訊觀測站":
company_info['stock_code'] = identifier
break
# 提取公司名稱
for fact in self.model.facts:
if 'EntityName' in fact.qname.localName or '公司名稱' in str(fact.concept.label(lang='zh-TW')):
company_info['company_name'] = fact.value
break
return company_info
def extract_financial_data(self) -> List[Dict]:
"""提取所有財務數據"""
financial_data = []
for fact in self.model.facts:
# 取得概念資訊
concept = fact.concept
concept_name = concept.qname.localName
# 取得中文標籤
label_zh = concept.label(lang='zh-TW')
if not label_zh:
label_zh = concept.label(lang='en')
# 取得數值
value = fact.value
# 取得單位
unit = None
if fact.unit:
measures = fact.unit.measures
if measures and len(measures[0]) > 0:
unit = measures[0][0].localName
# 取得期間
context = fact.context
if context.isInstant:
period_start = context.instant
period_end = context.instant
period_type = "時點"
else:
period_start = context.startDatetime
period_end = context.endDatetime
period_type = "期間"
# 取得精度
decimals = fact.decimals
# 正規化數值
if decimals and decimals.startswith('-'):
multiplier = 10 ** abs(int(decimals))
normalized_value = float(value) * multiplier if value else None
else:
normalized_value = float(value) if value else None
financial_data.append({
'concept_name': concept_name,
'label': label_zh,
'value': value,
'normalized_value': normalized_value,
'unit': unit,
'period_type': period_type,
'period_start': period_start.isoformat() if period_start else None,
'period_end': period_end.isoformat() if period_end else None,
'decimals': decimals
})
return financial_data
def extract_key_metrics(self) -> Dict:
"""提取關鍵財務指標"""
metrics = {}
# 定義要提取的關鍵概念
key_concepts = {
'Assets': '資產總額',
'Liabilities': '負債總額',
'Equity': '權益總額',
'Revenue': '營業收入',
'ProfitLoss': '本期淨利',
'OperatingIncome': '營業利益',
'CashAndCashEquivalents': '現金及約當現金',
'CurrentAssets': '流動資產',
'CurrentLiabilities': '流動負債'
}
for fact in self.model.facts:
concept_name = fact.concept.qname.localName
for key, label in key_concepts.items():
if key in concept_name:
# 正規化數值
value = fact.value
decimals = fact.decimals
if value and decimals and decimals.startswith('-'):
multiplier = 10 ** abs(int(decimals))
normalized_value = float(value) * multiplier
else:
normalized_value = float(value) if value else None
metrics[label] = {
'value': normalized_value,
'unit': 'TWD',
'period': fact.context.endDatetime.isoformat() if hasattr(fact.context, 'endDatetime') else None
}
return metrics
def calculate_financial_ratios(self, metrics: Dict) -> Dict:
"""計算財務比率"""
ratios = {}
try:
# 資產負債比率
assets = metrics.get('資產總額', {}).get('value', 0)
liabilities = metrics.get('負債總額', {}).get('value', 0)
equity = metrics.get('權益總額', {}).get('value', 0)
if assets:
ratios['負債比率'] = (liabilities / assets) * 100 if liabilities else 0
ratios['權益比率'] = (equity / assets) * 100 if equity else 0
# 流動性比率
current_assets = metrics.get('流動資產', {}).get('value', 0)
current_liabilities = metrics.get('流動負債', {}).get('value', 0)
if current_liabilities:
ratios['流動比率'] = (current_assets / current_liabilities) * 100
# 獲利能力比率
revenue = metrics.get('營業收入', {}).get('value', 0)
net_income = metrics.get('本期淨利', {}).get('value', 0)
operating_income = metrics.get('營業利益', {}).get('value', 0)
if revenue:
ratios['淨利率'] = (net_income / revenue) * 100 if net_income else 0
ratios['營業利益率'] = (operating_income / revenue) * 100 if operating_income else 0
if equity:
ratios['ROE'] = (net_income / equity) * 100 if net_income else 0
if assets:
ratios['ROA'] = (net_income / assets) * 100 if net_income else 0
except (ZeroDivisionError, TypeError) as e:
print(f"計算比率時發生錯誤: {e}")
return ratios
def export_to_json(self, output_file: str):
"""匯出為 JSON 格式"""
company_info = self.get_company_info()
metrics = self.extract_key_metrics()
ratios = self.calculate_financial_ratios(metrics)
output = {
'company_info': company_info,
'key_metrics': metrics,
'financial_ratios': ratios
}
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(output, f, ensure_ascii=False, indent=2)
print(f"已匯出至: {output_file}")
def close(self):
"""關閉模型"""
self.controller.modelManager.close()
# 使用範例
if __name__ == "__main__":
# 解析台積電財報
parser = TIFRSParser("tsmc_2024q4.xbrl")
# 取得公司資訊
company = parser.get_company_info()
print(f"公司: {company.get('company_name')} ({company.get('stock_code')})")
# 取得關鍵指標
metrics = parser.extract_key_metrics()
for label, data in metrics.items():
value = data['value']
if value:
print(f"{label}: {value:,.0f} 元")
# 計算財務比率
ratios = parser.calculate_financial_ratios(metrics)
for ratio_name, ratio_value in ratios.items():
print(f"{ratio_name}: {ratio_value:.2f}%")
# 匯出 JSON
parser.export_to_json("tsmc_analysis.json")
parser.close()
批次處理範例
import os
from concurrent.futures import ProcessPoolExecutor
from pathlib import Path
def process_single_file(xbrl_file: str) -> Dict:
"""處理單一 XBRL 檔案"""
try:
parser = TIFRSParser(xbrl_file)
company = parser.get_company_info()
metrics = parser.extract_key_metrics()
ratios = parser.calculate_financial_ratios(metrics)
parser.close()
return {
'file': xbrl_file,
'status': 'success',
'company': company,
'metrics': metrics,
'ratios': ratios
}
except Exception as e:
return {
'file': xbrl_file,
'status': 'error',
'error': str(e)
}
def batch_process_tifrs(directory: str, max_workers: int = 8):
"""
批次處理目錄中的所有 TIFRS 檔案
Args:
directory: 包含 XBRL 檔案的目錄
max_workers: 最大並行處理數
"""
# 找出所有 XBRL 檔案
xbrl_files = list(Path(directory).glob("**/*.xbrl"))
xbrl_files.extend(Path(directory).glob("**/*.xml"))
print(f"找到 {len(xbrl_files)} 個檔案")
# 並行處理
results = []
with ProcessPoolExecutor(max_workers=max_workers) as executor:
futures = [executor.submit(process_single_file, str(f)) for f in xbrl_files]
for i, future in enumerate(futures, 1):
result = future.result()
results.append(result)
if result['status'] == 'success':
company = result['company']
print(f"[{i}/{len(xbrl_files)}] ✓ {company.get('company_name', 'Unknown')}")
else:
print(f"[{i}/{len(xbrl_files)}] ✗ {result['file']}: {result['error']}")
# 統計
success_count = sum(1 for r in results if r['status'] == 'success')
print(f"\n完成: {success_count}/{len(results)}")
return results
# 執行批次處理
results = batch_process_tifrs("./xbrl_files", max_workers=8)
常見問題與解決方案
問題 1:繁體中文顯示亂碼
原因
- 編碼設定錯誤
- BOM (Byte Order Mark) 處理不當
解決方案
# 正確讀取 UTF-8 with BOM
import codecs
def read_xbrl_safely(file_path: str) -> str:
"""安全讀取可能包含 BOM 的 XBRL 檔案"""
# 先嘗試檢測 BOM
with open(file_path, 'rb') as f:
raw = f.read(4)
# UTF-8 with BOM
if raw.startswith(codecs.BOM_UTF8):
encoding = 'utf-8-sig'
# UTF-16 LE
elif raw.startswith(codecs.BOM_UTF16_LE):
encoding = 'utf-16-le'
# UTF-16 BE
elif raw.startswith(codecs.BOM_UTF16_BE):
encoding = 'utf-16-be'
else:
encoding = 'utf-8'
with open(file_path, 'r', encoding=encoding) as f:
return f.read()
問題 2:找不到台灣特定元素
原因
- 命名空間未正確載入
- QName 格式錯誤
解決方案
def find_tifrs_element(model: ModelXbrl, local_name: str, namespace_prefix: str = 'tifrs-bsta'):
"""
尋找 TIFRS 元素
Args:
model: Arelle ModelXbrl 物件
local_name: 元素本地名稱,如 'Assets'
namespace_prefix: 命名空間前綴,如 'tifrs-bsta'
Returns:
找到的 Fact 物件列表
"""
# 建立命名空間映射
namespace_map = {
'tifrs-bsta': 'http://www.xbrl.org/tw/fr/tifrs/bsta/2022-01-01',
'tifrs-pfs': 'http://www.xbrl.org/tw/fr/tifrs/pfs/2022-01-01',
'tifrs-cfs': 'http://www.xbrl.org/tw/fr/tifrs/cfs/2022-01-01',
'tifrs-cor': 'http://www.xbrl.org/tw/fr/tifrs/cor/2022-01-01',
}
namespace_uri = namespace_map.get(namespace_prefix)
if not namespace_uri:
raise ValueError(f"Unknown namespace prefix: {namespace_prefix}")
# 建立完整的 QName
from arelle.ModelValue import qname
target_qname = qname(namespace_uri, local_name)
# 尋找匹配的 Facts
matching_facts = []
for fact in model.facts:
if fact.qname == target_qname:
matching_facts.append(fact)
return matching_facts
# 使用範例
assets_facts = find_tifrs_element(model, 'Assets', 'tifrs-bsta')
for fact in assets_facts:
print(f"資產總額: {fact.value} (期間: {fact.context.endDatetime})")
問題 3:計算鏈路驗證失敗
原因
- 精度設定不一致
- 計算關係定義錯誤
- 數值捨入問題
解決方案
def validate_calculation_linkbase(model: ModelXbrl):
"""驗證計算鏈路"""
calc_linkbase = model.relationshipSet('http://www.xbrl.org/2003/arcrole/summation-item')
errors = []
for calc_rel in calc_linkbase.modelRelationships:
parent = calc_rel.fromModelObject
child = calc_rel.toModelObject
weight = calc_rel.weight
# 取得相關 Facts
parent_facts = [f for f in model.facts if f.concept == parent]
child_facts = [f for f in model.facts if f.concept == child]
# 檢查每個 Context
for parent_fact in parent_facts:
context_id = parent_fact.contextID
# 找出相同 Context 的子項目
related_children = [f for f in child_facts if f.contextID == context_id]
if not related_children:
continue
# 計算總和
calculated_sum = sum(float(f.value) * weight for f in related_children)
actual_value = float(parent_fact.value)
# 考慮精度差異(通常允許 0.01% 誤差)
tolerance = abs(actual_value) * 0.0001
difference = abs(calculated_sum - actual_value)
if difference > tolerance:
errors.append({
'parent': parent.label(lang='zh-TW'),
'context': context_id,
'calculated': calculated_sum,
'actual': actual_value,
'difference': difference
})
return errors
# 執行驗證
errors = validate_calculation_linkbase(model)
if errors:
print(f"發現 {len(errors)} 個計算錯誤:")
for error in errors:
print(f" {error['parent']} ({error['context']}): "
f"計算值 {error['calculated']:,.0f} vs 實際值 {error['actual']:,.0f} "
f"(差異: {error['difference']:,.0f})")
else:
print("計算鏈路驗證通過")
問題 4:效能優化
問題
- 大型 XBRL 檔案解析緩慢
- 批次處理效率低
優化策略
from functools import lru_cache
import multiprocessing as mp
class OptimizedTIFRSParser(TIFRSParser):
"""優化版 TIFRS 解析器"""
def __init__(self, xbrl_file: str, cache_size: int = 1000):
super().__init__(xbrl_file)
self.cache_size = cache_size
@lru_cache(maxsize=1000)
def get_concept_label_cached(self, qname: str, lang: str = 'zh-TW') -> str:
"""快取概念標籤查詢"""
concept = self.model.qnameConcepts.get(qname)
if concept:
return concept.label(lang=lang)
return qname
def extract_financial_data_optimized(self) -> List[Dict]:
"""優化的財務數據提取"""
financial_data = []
# 預先建立 Context 和 Unit 映射
context_map = {c.id: c for c in self.model.contexts.values()}
unit_map = {u.id: u for u in self.model.units.values()}
# 批次處理 Facts
for fact in self.model.facts:
concept_qname = str(fact.qname)
# 使用快取的標籤查詢
label = self.get_concept_label_cached(concept_qname, 'zh-TW')
# 快速查找 Context 和 Unit
context = context_map.get(fact.contextID)
unit = unit_map.get(fact.unitID) if fact.unitID else None
# 其餘處理邏輯...
financial_data.append({
'label': label,
'value': fact.value,
# ... 其他欄位
})
return financial_data
# 平行批次處理
def parallel_batch_process(file_list: List[str], num_workers: int = None):
"""平行批次處理 XBRL 檔案"""
if num_workers is None:
num_workers = mp.cpu_count()
with mp.Pool(processes=num_workers) as pool:
results = pool.map(process_single_file, file_list)
return results
驗證與合規工具
使用 Arelle 命令列驗證
# 基本驗證
arelle --file company_2024q4.xbrl --validate
# 使用 TIFRS 揭露系統驗證
arelle --file company_2024q4.xbrl \
--disclosureSystem tw-ifrs \
--validate
# 詳細驗證(包含計算)
arelle --file company_2024q4.xbrl \
--validate \
--calcDecimals \
--utrValidate \
--logFile validation_report.xml
# 產生人類可讀的驗證報告
arelle --file company_2024q4.xbrl \
--validate \
--logFile validation_report.txt \
--logFormat "%(message)s"
Python 自動化驗證腳本
import subprocess
import json
from pathlib import Path
def validate_tifrs_file(xbrl_file: str, output_dir: str = "./validation_reports"):
"""
驗證 TIFRS 檔案並產生報告
Args:
xbrl_file: XBRL 檔案路徑
output_dir: 驗證報告輸出目錄
"""
Path(output_dir).mkdir(parents=True, exist_ok=True)
file_name = Path(xbrl_file).stem
log_file = Path(output_dir) / f"{file_name}_validation.txt"
# 執行 Arelle 驗證
cmd = [
'arelle',
'--file', xbrl_file,
'--validate',
'--calcDecimals',
'--disclosureSystem', 'tw-ifrs',
'--logFile', str(log_file),
'--logFormat', '%(message)s'
]
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
# 解析驗證結果
with open(log_file, 'r', encoding='utf-8') as f:
log_content = f.read()
# 分析錯誤和警告
errors = [line for line in log_content.split('\n') if 'ERROR' in line]
warnings = [line for line in log_content.split('\n') if 'WARNING' in line]
validation_result = {
'file': xbrl_file,
'status': 'PASS' if not errors else 'FAIL',
'error_count': len(errors),
'warning_count': len(warnings),
'errors': errors[:10], # 只保留前 10 個錯誤
'warnings': warnings[:10],
'log_file': str(log_file)
}
return validation_result
except subprocess.TimeoutExpired:
return {
'file': xbrl_file,
'status': 'TIMEOUT',
'error': '驗證超時(超過 5 分鐘)'
}
except Exception as e:
return {
'file': xbrl_file,
'status': 'ERROR',
'error': str(e)
}
# 批次驗證
def batch_validate(directory: str):
"""批次驗證目錄中的所有 XBRL 檔案"""
xbrl_files = list(Path(directory).glob("**/*.xbrl"))
results = []
for xbrl_file in xbrl_files:
print(f"驗證: {xbrl_file.name}")
result = validate_tifrs_file(str(xbrl_file))
results.append(result)
if result['status'] == 'PASS':
print(f" ✓ 通過")
elif result['status'] == 'FAIL':
print(f" ✗ 失敗 ({result['error_count']} 個錯誤)")
else:
print(f" ⚠ {result['status']}: {result.get('error', '')}")
# 產生摘要報告
summary = {
'total': len(results),
'passed': sum(1 for r in results if r['status'] == 'PASS'),
'failed': sum(1 for r in results if r['status'] == 'FAIL'),
'errors': sum(1 for r in results if r['status'] == 'ERROR'),
'results': results
}
# 儲存 JSON 報告
with open('./validation_summary.json', 'w', encoding='utf-8') as f:
json.dump(summary, f, ensure_ascii=False, indent=2)
print(f"\n摘要: {summary['passed']}/{summary['total']} 通過驗證")
return summary
# 執行批次驗證
batch_validate("./xbrl_files")
工具與資源
推薦開發工具
| 工具 | 類型 | 用途 | 授權 |
|---|---|---|---|
| Arelle | 桌面應用/CLI | XBRL 解析、驗證、視覺化 | Apache 2.0 |
| python-xbrl | Python 套件 | XBRL 處理程式庫 | MIT |
| Altova XMLSpy | 商業軟體 | XML/XBRL 編輯器 | 商業授權 |
| oXygen XML Editor | 商業軟體 | XML/XBRL 開發環境 | 商業授權 |
| XBRL Analyst | Excel 插件 | Excel 中處理 XBRL | 商業授權 |
官方資源連結
重要連結
- 台灣證券交易所 XBRL 專區:https://www.twse.com.tw/xbrl
- 公開資訊觀測站:https://mops.twse.com.tw
- TIFRS 分類標準下載:(需透過證交所 XBRL 專區取得)
- Arelle 官網:https://arelle.org
- XBRL 國際組織:https://www.xbrl.org
Python 開發環境設定
# 建立虛擬環境
python -m venv xbrl_env
source xbrl_env/bin/activate # Linux/Mac
# xbrl_env\Scripts\activate # Windows
# 安裝必要套件
pip install arelle-release
pip install pandas
pip install lxml
pip install openpyxl # 如需 Excel 輸出
# 安裝開發工具
pip install pytest # 測試
pip install black # 程式碼格式化
pip install mypy # 類型檢查
# 建立 requirements.txt
pip freeze > requirements.txt
未來發展趨勢
ESG 資訊揭露整合
台灣金管會已規劃將 ESG(環境、社會、公司治理)資訊納入 XBRL 申報範圍。
即時報導(Real-time Reporting)
從季度報導逐步走向即時揭露:
發展階段
| 階段 | 時間框架 | 報導頻率 | 技術要求 |
|---|---|---|---|
| 第一階段(現行) | 2018-2025 | 季報 | 批次上傳 |
| 第二階段 | 2025-2028 | 月報 | API 整合 |
| 第三階段 | 2028-2030 | 即時 | 串流處理 |
| 第四階段 | 2030+ | 交易級 | 區塊鏈驗證 |
API 標準化
未來 API 可能規格
# 假設的未來 TIFRS API
import requests
# 取得公司最新財報
response = requests.get(
'https://api.twse.com.tw/xbrl/v2/company/2330/latest',
headers={'Authorization': 'Bearer YOUR_API_KEY'}
)
financial_data = response.json()
# 訂閱即時更新
from websocket import create_connection
ws = create_connection('wss://api.twse.com.tw/xbrl/v2/stream')
ws.send(json.dumps({
'action': 'subscribe',
'companies': ['2330', '2317'],
'events': ['financial_update', 'material_event']
}))
while True:
result = ws.recv()
data = json.loads(result)
print(f"收到更新: {data['company']} - {data['event']}")
結語
台灣 TIFRS 標準雖然基於國際 IFRS 架構,但在命名空間、語言標籤、會計科目和技術實作上都有顯著的在地化特色。對於開發 XBRL 處理系統的技術人員而言,深入理解這些差異是確保系統正確性、穩定性和效能的關鍵。
關鍵要點回顧
- 命名空間差異:TIFRS 使用 tifrs-* 系列命名空間,而非國際的 ifrs-*
- 繁體中文支援:完整的雙語標籤系統,優先使用 zh-TW 標籤
- 股本計算:台灣採用 10 元標準面額,影響股數和每股指標計算
- 貨幣尺度:注意 decimals 屬性的正確處理,常見千元、百萬元單位
- 實體識別:使用公開資訊觀測站的證券代號作為識別碼
最佳實踐建議
- 使用 Arelle 作為基礎 XBRL 處理引擎
- 實作完整的錯誤處理和驗證機制
- 採用快取策略提升批次處理效能
- 建立自動化測試確保資料正確性
- 保持對 TIFRS 標準更新的追蹤
隨著台灣持續推動財報數位化,以及 ESG 揭露、即時報導等新趨勢的發展,TIFRS 標準還會持續演進。掌握這些技術細節和實作要點,將有助於開發出穩健、高效的財報處理系統,為台灣金融科技生態系統貢獻價值。
延伸閱讀
產品解決方案
參考文獻
- XBRL 2.1 Specification - XBRL International
- Inline XBRL 1.1 Specification - XBRL International
- 台灣證券交易所 XBRL 實施指引
- TIFRS 分類標準技術規範
- Arelle 官方文件
標籤: #iXBRL #XBRL #TIFRS #台灣會計準則 #技術規範 #財報標準 #開發指南 #IFRS