Skip to content

Commit 95c5b38

Browse files
committed
Refactored output classes to use multiple inheritance allowing a single place to define which schema version support various attributes and elements.
1 parent bff5954 commit 95c5b38

File tree

3 files changed

+86
-121
lines changed

3 files changed

+86
-121
lines changed

cyclonedx/output/json.py

Lines changed: 15 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,31 @@
11
import json
2-
from abc import abstractmethod
2+
from abc import ABC, abstractmethod
33

44
from . import BaseOutput
5+
from .schema import BaseSchemaVersion, SchemaVersion1Dot0, SchemaVersion1Dot1, SchemaVersion1Dot2, SchemaVersion1Dot3
56
from ..model.cyclonedx import Component
67

78

8-
class Json(BaseOutput):
9+
class Json(BaseOutput, BaseSchemaVersion):
910

1011
def output_as_string(self) -> str:
1112
return json.dumps(self._get_json())
1213

1314
def output_to_file(self, filename: str):
14-
pass
15+
raise NotImplemented
1516

1617
def _get_json(self) -> dict:
1718
components = list(map(self._get_component_as_dict, self.get_bom().get_components()))
1819

1920
response = {
2021
"bomFormat": "CycloneDX",
21-
"specVersion": str(self._get_schema_version()),
22+
"specVersion": str(self.get_schema_version()),
2223
"serialNumber": self.get_bom().get_urn_uuid(),
2324
"version": 1,
2425
"components": components
2526
}
2627

27-
if self._bom_supports_metadata():
28+
if self.bom_supports_metadata():
2829
response['metadata'] = self._get_metadata_as_dict()
2930

3031
return response
@@ -37,59 +38,29 @@ def _get_component_as_dict(self, component: Component) -> dict:
3738
"purl": component.get_purl()
3839
}
3940

40-
if self._component_supports_author() and component.get_author() is not None:
41+
if self.component_supports_author() and component.get_author() is not None:
4142
c['author'] = component.get_author()
4243

4344
return c
4445

45-
def _bom_supports_metadata(self) -> bool:
46-
return True
47-
48-
def _component_supports_author(self) -> bool:
49-
return True
50-
5146
def _get_metadata_as_dict(self) -> dict:
5247
metadata = self.get_bom().get_metadata()
5348
return {
5449
"timestamp": metadata.get_timestamp().isoformat()
5550
}
5651

57-
@abstractmethod
58-
def _get_schema_version(self) -> str:
59-
pass
60-
61-
62-
class JsonV1Dot0(Json):
63-
64-
def _get_schema_version(self) -> str:
65-
return '1.0'
66-
67-
def _bom_supports_metadata(self) -> bool:
68-
return False
69-
70-
def _component_supports_author(self) -> bool:
71-
return False
72-
73-
74-
class JsonV1Dot1(Json):
75-
76-
def _get_schema_version(self) -> str:
77-
return '1.1'
78-
79-
def _bom_supports_metadata(self) -> bool:
80-
return False
8152

82-
def _component_supports_author(self) -> bool:
83-
return False
53+
class JsonV1Dot0(Json, SchemaVersion1Dot0):
54+
pass
8455

8556

86-
class JsonV1Dot2(Json):
57+
class JsonV1Dot1(Json, SchemaVersion1Dot1):
58+
pass
8759

88-
def _get_schema_version(self) -> str:
89-
return '1.2'
9060

61+
class JsonV1Dot2(Json, SchemaVersion1Dot2):
62+
pass
9163

92-
class JsonV1Dot3(Json):
9364

94-
def _get_schema_version(self) -> str:
95-
return '1.3'
65+
class JsonV1Dot3(Json, SchemaVersion1Dot3):
66+
pass

cyclonedx/output/schema.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from abc import ABC, abstractmethod
2+
3+
4+
class BaseSchemaVersion(ABC):
5+
6+
def bom_supports_metadata(self) -> bool:
7+
return True
8+
9+
def component_supports_author(self) -> bool:
10+
return True
11+
12+
def component_supports_bom_ref(self) -> bool:
13+
return True
14+
15+
def get_schema_version(self) -> str:
16+
pass
17+
18+
19+
class SchemaVersion1Dot3(BaseSchemaVersion):
20+
21+
def get_schema_version(self) -> str:
22+
return '1.3'
23+
24+
25+
class SchemaVersion1Dot2(BaseSchemaVersion):
26+
27+
def get_schema_version(self) -> str:
28+
return '1.2'
29+
30+
31+
class SchemaVersion1Dot1(BaseSchemaVersion):
32+
33+
def bom_supports_metadata(self) -> bool:
34+
return False
35+
36+
def component_supports_author(self) -> bool:
37+
return False
38+
39+
def get_schema_version(self) -> str:
40+
return '1.1'
41+
42+
43+
class SchemaVersion1Dot0(BaseSchemaVersion):
44+
45+
def bom_supports_metadata(self) -> bool:
46+
return False
47+
48+
def component_supports_author(self) -> bool:
49+
return False
50+
51+
def component_supports_bom_ref(self) -> bool:
52+
return False
53+
54+
def get_schema_version(self) -> str:
55+
return '1.0'

cyclonedx/output/xml.py

Lines changed: 16 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,20 @@
1-
from abc import abstractmethod
21
from xml.etree import ElementTree
32

43
from . import BaseOutput
4+
from .schema import BaseSchemaVersion, SchemaVersion1Dot0, SchemaVersion1Dot1, SchemaVersion1Dot2, SchemaVersion1Dot3
55
from ..model.cyclonedx import Component
66

77

8-
def _xml_pretty_print(elem: ElementTree.Element, level: int = 0) -> ElementTree.Element:
9-
"""
10-
Helper method lifed from cyclonedx-python original project for formatting
11-
XML without using any XML-libraries.
12-
13-
NOTE: This method is recursive.
14-
15-
:param elem:
16-
:param level:
17-
:return:
18-
"""
19-
i = "\n" + level * " "
20-
if len(elem):
21-
if not elem.text or not elem.text.strip():
22-
elem.text = i + " "
23-
if not elem.tail or not elem.tail.strip():
24-
elem.tail = i
25-
for elem in elem:
26-
_xml_pretty_print(elem, level + 1)
27-
if not elem.tail or not elem.tail.strip():
28-
elem.tail = i
29-
else:
30-
if level and (not elem.tail or not elem.tail.strip()):
31-
elem.tail = i
32-
return elem
33-
34-
35-
class Xml(BaseOutput):
8+
class Xml(BaseOutput, BaseSchemaVersion):
369
XML_VERSION_DECLARATION: str = '<?xml version="1.0" encoding="UTF-8"?>'
3710

3811
def get_target_namespace(self) -> str:
39-
return 'http://cyclonedx.org/schema/bom/{}'.format(self._get_schema_version())
12+
return 'http://cyclonedx.org/schema/bom/{}'.format(self.get_schema_version())
4013

4114
def output_as_string(self) -> str:
4215
bom = self._get_bom_root_element()
4316

44-
if self._bom_supports_metadata():
17+
if self.bom_supports_metadata():
4518
bom = self._add_metadata(bom=bom)
4619

4720
components = ElementTree.SubElement(bom, 'components')
@@ -53,18 +26,21 @@ def output_as_string(self) -> str:
5326
def output_to_file(self, filename: str):
5427
pass
5528

29+
def _component_supports_bom_ref_attribute(self) -> bool:
30+
return True
31+
5632
def _get_bom_root_element(self) -> ElementTree.Element:
5733
return ElementTree.Element('bom', {'xmlns': self.get_target_namespace(), 'version': '1',
5834
'serialNumber': self.get_bom().get_urn_uuid()})
5935

6036
def _get_component_as_xml_element(self, component: Component) -> ElementTree.Element:
6137
element_attributes = {'type': component.get_type().value}
62-
if self._component_supports_bom_ref_attribute():
38+
if self.component_supports_bom_ref():
6339
element_attributes['bom-ref'] = component.get_purl()
6440

6541
component_element = ElementTree.Element('component', element_attributes)
6642

67-
if self._component_supports_author() and component.get_author() is not None:
43+
if self.component_supports_author() and component.get_author() is not None:
6844
ElementTree.SubElement(component_element, 'author').text = component.get_author()
6945

7046
# if publisher and publisher != "UNKNOWN":
@@ -104,57 +80,20 @@ def _add_metadata(self, bom: ElementTree.Element) -> ElementTree.Element:
10480
ElementTree.SubElement(metadata_e, 'timestamp').text = self.get_bom().get_metadata().get_timestamp().isoformat()
10581
return bom
10682

107-
def _bom_supports_metadata(self) -> bool:
108-
return True
109-
110-
def _component_supports_author(self) -> bool:
111-
return True
112-
113-
def _component_supports_bom_ref_attribute(self) -> bool:
114-
return True
115-
116-
@abstractmethod
117-
def _get_schema_version(self) -> str:
118-
pass
119-
12083

121-
class XmlV1Dot0(Xml):
84+
class XmlV1Dot0(Xml, SchemaVersion1Dot0):
12285

12386
def _get_bom_root_element(self) -> ElementTree.Element:
12487
return ElementTree.Element('bom', {'xmlns': self.get_target_namespace(), 'version': '1'})
12588

126-
def _get_schema_version(self) -> str:
127-
return '1.0'
128-
129-
def _bom_supports_metadata(self) -> bool:
130-
return False
131-
132-
def _component_supports_bom_ref_attribute(self) -> bool:
133-
return False
134-
135-
def _component_supports_author(self) -> bool:
136-
return False
137-
138-
139-
class XmlV1Dot1(Xml):
140-
141-
def _get_schema_version(self) -> str:
142-
return '1.1'
143-
144-
def _bom_supports_metadata(self) -> bool:
145-
return False
146-
147-
def _component_supports_author(self) -> bool:
148-
return False
149-
15089

151-
class XmlV1Dot2(Xml):
90+
class XmlV1Dot1(Xml, SchemaVersion1Dot1):
91+
pass
15292

153-
def _get_schema_version(self) -> str:
154-
return '1.2'
15593

94+
class XmlV1Dot2(Xml, SchemaVersion1Dot2):
95+
pass
15696

157-
class XmlV1Dot3(Xml):
15897

159-
def _get_schema_version(self) -> str:
160-
return '1.3'
98+
class XmlV1Dot3(Xml, SchemaVersion1Dot3):
99+
pass

0 commit comments

Comments
 (0)