diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..d9b1f4b --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +# These are supported funding model platforms +buy_me_a_coffee: manusimidt diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..c136d39 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,14 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**XBRL document** +Please provide a link to the XBRL document(s) with which you have encountered the issue + +**Describe the bug** +A clear and concise description of what the bug is. diff --git a/.gitignore b/.gitignore index 8906bfc..771b95a 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 .idea/ +.notes.md # Gradle and Maven with auto-import # When using Gradle or Maven with auto-import, you should exclude module files, diff --git a/requirements.txt b/requirements.txt index 352fe16..5212d6f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -setuptools==65.5.1 -requests~=2.31.0 -urllib3~=1.26.11 +setuptools==70.2.0 +requests~=2.32.3 +urllib3~=2.2.2 diff --git a/setup.py b/setup.py index 59365f2..38a43e4 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ def read(filename): setup( name="py-xbrl", - version="2.2.10", + version="2.2.12", url="https://github.com/manusimidt/xbrl_parser", license='GNU General Public License v3 (GPLv3)', author="Manuel Schmidt", diff --git a/tests/test_local_taxonomy.py b/tests/test_local_taxonomy.py index 81bd203..1737361 100644 --- a/tests/test_local_taxonomy.py +++ b/tests/test_local_taxonomy.py @@ -18,10 +18,10 @@ def test_parse_taxonomy(self): cache_dir: str = './cache/' cache: HttpCache = HttpCache(cache_dir) print(f"Saving to {cache_dir}") - imported_schema_uris = set() + extension_schema_path: str = './tests/data/example.xsd' # extension_schema_path: str = './data/example.xsd' - tax: TaxonomySchema = parse_taxonomy(extension_schema_path, cache, imported_schema_uris = set()) + tax: TaxonomySchema = parse_taxonomy(extension_schema_path, cache) print(tax) srt_tax: TaxonomySchema = tax.get_taxonomy('http://fasb.org/srt/2020-01-31') self.assertTrue(srt_tax) @@ -32,4 +32,4 @@ def test_parse_taxonomy(self): if __name__ == '__main__': - unittest.main() + unittest.main() \ No newline at end of file diff --git a/xbrl/__init__.py b/xbrl/__init__.py index 6bf3415..130bcb7 100644 --- a/xbrl/__init__.py +++ b/xbrl/__init__.py @@ -76,7 +76,7 @@ class ContextParseException(InstanceParseException): pass -__version__ = '2.2.10' +__version__ = '2.2.12' __author__ = 'Manuel Schmidt ' __all__ = [ XbrlParseException, diff --git a/xbrl/instance.py b/xbrl/instance.py index 81d01df..4f634de 100644 --- a/xbrl/instance.py +++ b/xbrl/instance.py @@ -36,21 +36,71 @@ } -class ExplicitMember: +class AbstractMember(abc.ABC): + def __init__(self, dimension: Concept) -> None: + self.dimension = dimension + + def __str__(self) -> str: + return "Dimension {}".format(self.dimension.name) + + def to_dict(self): + return { + 'dimension': self.dimension.to_dict(), + } + + def to_json(self): + return json.dumps(self.to_dict()) + + +class TypedMember(AbstractMember): + """ + Representation of a typed member in xbrl. + + XML Example: + + 2024-06-30 + + """ + def __init__(self, dimension: Concept, domain: List[str]) -> None: + super().__init__(dimension) + self.domain = domain + + def __str__(self) -> str: + return "{} on dimension {}".format(self.domain, self.dimension.name) + + def to_dict(self): + return { + 'dimension': self.dimension.to_dict(), + 'domain': self.domain + } + + def to_json(self): + return json.dumps(self.to_dict()) + + +class ExplicitMember(AbstractMember): """ Representation of an explicit member in xbrl. XML Example: aapl:EuropeSegmentMember """ - def __init__(self, dimension: Concept, member: Concept) -> None: - self.dimension = dimension + super().__init__(dimension) self.member = member def __str__(self) -> str: return "{} on dimension {}".format(self.member.name, self.dimension.name) + def to_dict(self): + return { + 'dimension': self.dimension.to_dict(), + 'member': self.member.to_dict() + } + + def to_json(self): + return json.dumps(self.to_dict()) + class AbstractContext(abc.ABC): """ @@ -64,7 +114,7 @@ class AbstractContext(abc.ABC): def __init__(self, xml_id: str, entity: str) -> None: self.xml_id: str = xml_id self.entity: str = entity - self.segments: List[ExplicitMember] = [] + self.segments: List[AbstractMember] = [] class InstantContext(AbstractContext): @@ -201,9 +251,11 @@ def json(self, **kwargs) -> dict: if 'dimensions' not in kwargs: kwargs['dimensions'] = {} kwargs['dimensions']['concept'] = self.concept.name kwargs['dimensions']['entity'] = self.context.entity + kwargs['dimensions']['contextId'] = self.context.xml_id kwargs['dimensions']['period'] = period for segment in self.context.segments: - kwargs['dimensions'][segment.dimension.name] = segment.member.name + if isinstance(segment, ExplicitMember): + kwargs['dimensions'][segment.dimension.name] = segment.member.name return kwargs @@ -657,6 +709,22 @@ def _parse_context_elements(context_elements: List[ET.Element], ns_map: dict, ta # add the explicit member to the context context.segments.append(ExplicitMember(dimension_concept, member_concept)) + for typed_member_element in segment.findall('xbrldi:typedMember', NAME_SPACES): + _update_ns_map(ns_map, typed_member_element.attrib['ns_map']) + dimension_prefix, dimension_concept_name = typed_member_element.attrib['dimension'].strip().split(':') + # get the taxonomy where the dimension attribute is defined + dimension_tax = taxonomy.get_taxonomy(ns_map[dimension_prefix]) + # check if the taxonomy was found + if dimension_tax is None: + # try to subsequently load the taxonomy + dimension_tax = _load_common_taxonomy(cache, ns_map[dimension_prefix], taxonomy) + dimension_concept: Concept = dimension_tax.concepts[dimension_tax.name_id_map[dimension_concept_name]] + domain: List[str] = [] + for child in typed_member_element: + domain.append(child.text.strip()) + + context.segments.append(TypedMember(dimension_concept, domain)) + context_dict[context_id] = context return context_dict diff --git a/xbrl/taxonomy.py b/xbrl/taxonomy.py index f9b078c..8af967e 100644 --- a/xbrl/taxonomy.py +++ b/xbrl/taxonomy.py @@ -168,6 +168,7 @@ "http://fasb.org/srt/2021-01-31": "http://xbrl.fasb.org/srt/2021/elts/srt-2021-01-31.xsd", "http://fasb.org/srt/2022": "https://xbrl.fasb.org/srt/2022/elts/srt-2022.xsd", "http://fasb.org/srt/2023": "https://xbrl.fasb.org/srt/2023/elts/srt-2023.xsd", + "http://fasb.org/srt/2024": "https://xbrl.fasb.org/srt/2024/elts/srt-2024.xsd", "http://fasb.org/srt-roles/2018-01-31": "http://xbrl.fasb.org/srt/2018/elts/srt-roles-2018-01-31.xsd", "http://fasb.org/srt-roles/2019-01-31": "http://xbrl.fasb.org/srt/2019/elts/srt-roles-2019-01-31.xsd", "http://fasb.org/srt-roles/2020-01-31": "http://xbrl.fasb.org/srt/2020/elts/srt-roles-2020-01-31.xsd", @@ -190,6 +191,7 @@ "http://fasb.org/us-gaap/2022-01-31": "https://xbrl.fasb.org/us-gaap/2022/elts/us-gaap-2022.xsd", "http://fasb.org/us-gaap/2022": "https://xbrl.fasb.org/us-gaap/2022/elts/us-gaap-2022.xsd", "http://fasb.org/us-gaap/2023": "https://xbrl.fasb.org/us-gaap/2023/elts/us-gaap-2023.xsd", + "http://fasb.org/us-gaap/2024": "https://xbrl.fasb.org/us-gaap/2024/elts/us-gaap-2024.xsd", "http://fasb.org/us-roles/2011-01-31": "http://xbrl.fasb.org/us-gaap/2011/elts/us-roles-2011-01-31.xsd", "http://fasb.org/us-roles/2012-01-31": "http://xbrl.fasb.org/us-gaap/2012/elts/us-roles-2012-01-31.xsd", "http://fasb.org/us-roles/2013-01-31": "http://xbrl.fasb.org/us-gaap/2013/elts/us-roles-2013-01-31.xsd", @@ -281,6 +283,7 @@ "http://xbrl.sec.gov/dei/2021": "https://xbrl.sec.gov/dei/2021/dei-2021.xsd", "http://xbrl.sec.gov/dei/2022": "https://xbrl.sec.gov/dei/2022/dei-2022.xsd", "http://xbrl.sec.gov/dei/2023": "https://xbrl.sec.gov/dei/2023/dei-2023.xsd", + "http://xbrl.sec.gov/dei/2024": "https://xbrl.sec.gov/dei/2024/dei-2024.xsd", "http://xbrl.sec.gov/dei/2021q4": "https://xbrl.sec.gov/dei/2021q4/dei-2021q4.xsd", "http://xbrl.sec.gov/dei-def/2021": "https://xbrl.sec.gov/dei/2021/dei-2021_def.xsd", "http://xbrl.sec.gov/dei-entire/2021": "https://xbrl.sec.gov/dei/2021/dei-entire-2021.xsd", @@ -603,7 +606,7 @@ def parse_taxonomy_url(schema_url: str, cache: HttpCache, imported_schema_uris: return parse_taxonomy(schema_path, cache, imported_schema_uris, schema_url) -def parse_taxonomy(schema_path: str, cache: HttpCache, imported_schema_uris : set, schema_url: str or None = None) -> TaxonomySchema: +def parse_taxonomy(schema_path: str, cache: HttpCache, imported_schema_uris : set = set(), schema_url: str or None = None) -> TaxonomySchema: """ Parses a taxonomy schema file. diff --git a/xbrl/transformations/__init__.py b/xbrl/transformations/__init__.py index 098af82..9913503 100644 --- a/xbrl/transformations/__init__.py +++ b/xbrl/transformations/__init__.py @@ -315,7 +315,7 @@ def durWordSen(arg: str) -> str: def numWordSen(arg: str) -> str: - if arg == 'no' or arg == 'none': + if arg == 'no' or arg == 'none' or arg == 'nil': return '0' else: arg = arg.replace(' and ', ' ')