1
2# Copyright (c) Xavier Figueroa
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#     http://www.apache.org/licenses/LICENSE-2.0
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS,
9# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10# See the License for the specific language governing permissions and
11# limitations under the License.
12
13from __future__ import absolute_import
14from __future__ import print_function
15from __future__ import unicode_literals
16
17from rdflib import Literal
18
19from spdx import document
20
21
22class BaseWriter(object):
23    """
24    Base class for all Writer classes.
25    Provide utility functions and stores shared fields.
26    - document: spdx.document class. Source of data to be written
27    - document_object: python dictionary representation of the entire spdx.document
28    """
29
30    def __init__(self, document):
31        self.document = document
32        self.document_object = dict()
33
34    def license(self, license_field):
35        """
36        Return a string representation of a license or spdx.utils special object
37        """
38        if isinstance(license_field, (document.LicenseDisjunction, document.LicenseConjunction)):
39            return '({})'.format(license_field)
40
41        if isinstance(license_field, document.License):
42            license_str = license_field.identifier.__str__()
43        else:
44            license_str = license_field.__str__()
45        return license_str
46
47    def checksum(self, checksum_field):
48        """
49        Return a dictionary representation of a spdx.checksum.Algorithm object
50        """
51        checksum_object = dict()
52        checksum_object['algorithm'] = 'checksumAlgorithm_' + checksum_field.identifier.lower()
53        checksum_object['value'] = checksum_field.value
54        return checksum_object
55
56    def spdx_id(self, spdx_id_field):
57        return spdx_id_field.__str__().split('#')[-1]
58
59class CreationInfoWriter(BaseWriter):
60    """
61    Represent spdx.creationinfo as json-serializable objects
62    """
63
64    def __init__(self, document):
65        super(CreationInfoWriter, self).__init__(document)
66
67    def create_creation_info(self):
68        creation_info_object = dict()
69        creation_info = self.document.creation_info
70        creation_info_object['creators'] = list(map(str, creation_info.creators))
71        creation_info_object['created'] = creation_info.created_iso_format
72
73        if creation_info.license_list_version:
74            creation_info_object['licenseListVersion'] = '{0}.{1}'.format(creation_info.license_list_version.major, creation_info.license_list_version.minor)
75
76        if creation_info.has_comment:
77            creation_info_object['comment'] = creation_info.comment
78
79        return creation_info_object
80
81class PackageWriter(BaseWriter):
82    """
83    Represent spdx.package as python objects
84    """
85
86    def __init__(self, document):
87        super(PackageWriter, self).__init__(document)
88
89    def package_verification_code(self, package):
90        """
91        Represent the package verification code information as
92        as python dictionary
93        """
94
95        package_verification_code_object = dict()
96
97        package_verification_code_object['value'] = package.verif_code
98
99        if package.verif_exc_files:
100            package_verification_code_object['excludedFilesNames'] = package.verif_exc_files
101
102        return package_verification_code_object
103
104    def create_package_info(self):
105        package_object = dict()
106        package = self.document.package
107        package_object['id'] = self.spdx_id(package.spdx_id)
108        package_object['name'] = package.name
109        package_object['downloadLocation'] = package.download_location.__str__()
110        package_object['packageVerificationCode'] = self.package_verification_code(package)
111        package_object['licenseConcluded'] = self.license(package.conc_lics)
112        package_object['licenseInfoFromFiles'] = list(map(self.license, package.licenses_from_files))
113        package_object['licenseDeclared'] = self.license(package.license_declared)
114        package_object['copyrightText'] = package.cr_text.__str__()
115
116        if package.has_optional_field('version'):
117            package_object['versionInfo'] = package.version
118
119        if package.has_optional_field('summary'):
120            package_object['summary'] = package.summary
121
122        if package.has_optional_field('source_info'):
123            package_object['sourceInfo'] = package.source_info
124
125        if package.has_optional_field('file_name'):
126            package_object['packageFileName'] = package.file_name
127
128        if package.has_optional_field('supplier'):
129            package_object['supplier'] = package.supplier.__str__()
130
131        if package.has_optional_field('originator'):
132            package_object['originator'] = package.originator.__str__()
133
134        if package.has_optional_field('check_sum'):
135            package_object['checksums'] = [self.checksum(package.check_sum)]
136            package_object['sha1'] = package.check_sum.value
137
138        if package.has_optional_field('description'):
139            package_object['description'] = package.description
140
141        if package.has_optional_field('license_comment'):
142            package_object['licenseComments'] = package.license_comment
143
144        if package.has_optional_field('homepage'):
145            package_object['homepage'] = package.homepage.__str__()
146
147        return package_object
148
149class FileWriter(BaseWriter):
150    """
151    Represent spdx.file as json-serializable objects
152    """
153
154    def __init__(self, document):
155        super(FileWriter, self).__init__(document)
156
157    def create_artifact_info(self, file):
158        """
159        Create the artifact json-serializable representation from a spdx.file.File object
160        """
161        artifact_of_objects = []
162
163        for i in range(len(file.artifact_of_project_name)):
164            artifact_of_object = dict()
165            artifact_of_object['name'] = file.artifact_of_project_name[i].__str__()
166            artifact_of_object['homePage'] = file.artifact_of_project_home[i].__str__()
167            artifact_of_object['projectUri'] = file.artifact_of_project_uri[i].__str__()
168            artifact_of_objects.append(artifact_of_object)
169
170        return artifact_of_objects
171
172    def create_file_info(self):
173        file_types = { 1: 'fileType_source', 2: 'fileType_binary', 3: 'fileType_archive', 4: 'fileType_other'}
174        file_objects = []
175        files = self.document.files
176
177        for file in files:
178            file_object = dict()
179
180            file_object['name'] = file.name
181            file_object['id'] = self.spdx_id(file.spdx_id)
182            file_object['checksums'] = [self.checksum(file.chk_sum)]
183            file_object['licenseConcluded'] = self.license(file.conc_lics)
184            file_object['licenseInfoFromFiles'] = list(map(self.license, file.licenses_in_file))
185            file_object['copyrightText'] = file.copyright.__str__()
186            file_object['sha1'] = file.chk_sum.value
187
188            if file.has_optional_field('comment'):
189                file_object['comment'] = file.comment
190
191            if file.has_optional_field('type'):
192                file_object['fileTypes'] = [file_types.get(file.type)]
193
194            if file.has_optional_field('license_comment'):
195                file_object['licenseComments'] = file.license_comment
196
197            if file.has_optional_field('notice'):
198                file_object['noticeText'] = file.notice
199
200            if file.contributors:
201                file_object['fileContributors'] = file.contributors.__str__()
202
203            if file.dependencies:
204                file_object['fileDependencies'] = file.dependencies
205
206            valid_artifacts = file.artifact_of_project_name and len(file.artifact_of_project_name) == len(file.artifact_of_project_home) and len(file.artifact_of_project_home) == len(file.artifact_of_project_uri)
207
208            if valid_artifacts:
209                file_object['artifactOf'] = self.create_artifact_info(file)
210
211            file_objects.append({"File": file_object})
212
213        return file_objects
214
215class ReviewInfoWriter(BaseWriter):
216    """
217    Represent spdx.review as json-serializable objects
218    """
219
220    def __init__(self, document):
221        super(ReviewInfoWriter, self).__init__(document)
222
223    def create_review_info(self):
224        review_info_objects = []
225        reviews = self.document.reviews
226
227        for review in reviews:
228            review_object = dict()
229            review_object['reviewer'] = review.reviewer.__str__()
230            review_object['reviewDate'] = review.review_date_iso_format
231            if review.has_comment:
232                review_object['comment'] = review.comment
233
234            review_info_objects.append(review_object)
235
236        return review_info_objects
237
238class AnnotationInfoWriter(BaseWriter):
239    """
240    Represent spdx.annotation as json-serializable objects
241    """
242
243    def __init__(self, document):
244        super(AnnotationInfoWriter, self).__init__(document)
245
246    def create_annotation_info(self):
247        """
248        The way how tools-python manages its models makes difficult to classify
249        annotations (by document, files and packages) and some of them could end up omitted.
250        This method sets every annotation as part of the spdx document itself,
251        avoiding them to be omitted.
252        """
253        annotation_objects = []
254
255        for annotation in self.document.annotations:
256            annotation_object = dict()
257            annotation_object['id'] = self.spdx_id(annotation.spdx_id)
258            annotation_object['annotator'] = annotation.annotator.__str__()
259            annotation_object['annotationDate'] = annotation.annotation_date_iso_format
260            annotation_object['annotationType'] = annotation.annotation_type
261            annotation_object['comment'] = annotation.comment
262
263            annotation_objects.append(annotation_object)
264
265        return annotation_objects
266
267class SnippetWriter(BaseWriter):
268    """
269    Represent spdx.annotation as json-serializable objects
270    """
271    def __init__(self, document):
272        super(SnippetWriter, self).__init__(document)
273
274    def create_snippet_info(self):
275        snippet_info_objects = []
276        snippets = self.document.snippet
277
278        for snippet in snippets:
279            snippet_object = dict()
280            snippet_object['id'] = self.spdx_id(snippet.spdx_id)
281            snippet_object['copyrightText'] = snippet.copyright
282            snippet_object['fileId'] = self.spdx_id(snippet.snip_from_file_spdxid)
283            snippet_object['licenseConcluded'] = self.license(snippet.conc_lics)
284            snippet_object['licenseInfoFromSnippet'] = list(map(self.license, snippet.licenses_in_snippet))
285
286            if snippet.has_optional_field('name'):
287                snippet_object['name'] = snippet.name
288
289            if snippet.has_optional_field('comment'):
290                snippet_object['comment'] = snippet.comment
291
292            if snippet.has_optional_field('license_comment'):
293                snippet_object['licenseComments'] = snippet.license_comment
294
295            snippet_info_objects.append(snippet_object)
296
297        return snippet_info_objects
298
299class ExtractedLicenseWriter(BaseWriter):
300    """
301    Represent spdx.document.ExtractedLicense as json-serializable objects
302    """
303
304    def __init__(self, document):
305        super(ExtractedLicenseWriter, self).__init__(document)
306
307    def create_extracted_license(self):
308        extracted_license_objects = []
309        extracted_licenses = self.document.extracted_licenses
310
311        for extracted_license in extracted_licenses:
312            extracted_license_object = dict()
313
314            if isinstance(extracted_license.identifier, Literal):
315                extracted_license_object['licenseId'] = extracted_license.identifier.toPython()
316            else:
317                extracted_license_object['licenseId'] = extracted_license.identifier
318
319            if isinstance(extracted_license.text, Literal):
320                extracted_license_object['extractedText'] = extracted_license.text.toPython()
321            else:
322                extracted_license_object['extractedText'] = extracted_license.text
323
324            if extracted_license.full_name:
325                if isinstance(extracted_license.full_name, Literal):
326                    extracted_license_object['name'] = extracted_license.full_name.toPython()
327                else:
328                    extracted_license_object['name'] = extracted_license.full_name
329
330            if extracted_license.cross_ref:
331                if isinstance(extracted_license.cross_ref, Literal):
332                    extracted_license_object['seeAlso'] = extracted_license.cross_ref.toPython()
333                else:
334                    extracted_license_object['seeAlso'] = extracted_license.cross_ref
335
336            if extracted_license.comment:
337                if isinstance(extracted_license.comment, Literal):
338                    extracted_license_object['comment'] = extracted_license.comment.toPython()
339                else:
340                    extracted_license_object['comment'] = extracted_license.comment
341
342            extracted_license_objects.append(extracted_license_object)
343
344        return extracted_license_objects
345
346class Writer(CreationInfoWriter, ReviewInfoWriter, FileWriter, PackageWriter,
347    AnnotationInfoWriter, SnippetWriter, ExtractedLicenseWriter):
348    """
349    Wrapper for the other writers.
350    Represent a whole SPDX Document as json-serializable objects to then
351    be written as json or yaml files.
352    """
353
354    def __init__(self, document):
355        super(Writer, self).__init__(document)
356
357    def create_ext_document_references(self):
358        """
359        Create the External Document References json-serializable representation
360        """
361        ext_document_references_field = self.document.ext_document_references
362        ext_document_reference_objects = []
363        for ext_document_reference in ext_document_references_field:
364            ext_document_reference_object = dict()
365            ext_document_reference_object['externalDocumentId'] = ext_document_reference.external_document_id
366            ext_document_reference_object['spdxDocumentNamespace'] = ext_document_reference.spdx_document_uri
367
368            ext_document_reference_object['checksum'] = self.checksum(ext_document_reference.check_sum)
369
370            ext_document_reference_objects.append(ext_document_reference_object)
371
372        return ext_document_reference_objects
373
374    def create_document(self):
375        self.document_object = dict()
376
377        self.document_object['specVersion'] = self.document.version.__str__()
378        self.document_object['namespace'] = self.document.namespace.__str__()
379        self.document_object['creationInfo'] = self.create_creation_info()
380        self.document_object['dataLicense'] = self.license(self.document.data_license)
381        self.document_object['id'] = self.spdx_id(self.document.spdx_id)
382        self.document_object['name'] = self.document.name
383
384        package_info_object = self.create_package_info()
385        package_info_object['files'] = self.create_file_info()
386
387        self.document_object['documentDescribes'] = [{'Package': package_info_object}]
388
389        if self.document.has_comment:
390            self.document_object['comment'] = self.document.comment
391
392        if self.document.ext_document_references:
393            self.document_object['externalDocumentRefs'] = self.create_ext_document_references()
394
395        if self.document.extracted_licenses:
396            self.document_object['extractedLicenseInfos'] = self.create_extracted_license()
397
398        if self.document.reviews:
399            self.document_object['reviewers'] = self.create_review_info()
400
401        if self.document.snippet:
402            self.document_object['snippets'] = self.create_snippet_info()
403
404        if self.document.annotations:
405            self.document_object['annotations'] = self.create_annotation_info()
406
407        return {'Document' : self.document_object}
408