1 2# Copyright (c) 2014 Ahmed H. Ismail 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 BNode 18from rdflib import Graph 19from rdflib import Literal 20from rdflib import Namespace 21from rdflib import RDF 22from rdflib import RDFS 23from rdflib import URIRef 24from rdflib.compare import to_isomorphic 25 26from spdx import file 27from spdx import document 28from spdx import config 29from spdx import utils 30from spdx.writers.tagvalue import InvalidDocumentError 31 32 33class BaseWriter(object): 34 """ 35 Base class for all Writer classes. 36 Provide utility functions and stores shared fields. 37 """ 38 39 def __init__(self, document, out): 40 self.document = document 41 self.out = out 42 self.doap_namespace = Namespace('http://usefulinc.com/ns/doap#') 43 self.spdx_namespace = Namespace("http://spdx.org/rdf/terms#") 44 self.graph = Graph() 45 46 def create_checksum_node(self, chksum): 47 """ 48 Return a node representing spdx.checksum. 49 """ 50 chksum_node = BNode() 51 type_triple = (chksum_node, RDF.type, self.spdx_namespace.Checksum) 52 self.graph.add(type_triple) 53 algorithm_triple = (chksum_node, self.spdx_namespace.algorithm, Literal(chksum.identifier)) 54 self.graph.add(algorithm_triple) 55 value_triple = (chksum_node, self.spdx_namespace.checksumValue, Literal(chksum.value)) 56 self.graph.add(value_triple) 57 return chksum_node 58 59 def to_special_value(self, value): 60 """ 61 Return proper spdx term or Literal 62 """ 63 if isinstance(value, utils.NoAssert): 64 return self.spdx_namespace.noassertion 65 elif isinstance(value, utils.SPDXNone): 66 return self.spdx_namespace.none 67 else: 68 return Literal(value) 69 70 71class LicenseWriter(BaseWriter): 72 """ 73 Handle all License classes from spdx.document module. 74 """ 75 76 def __init__(self, document, out): 77 super(LicenseWriter, self).__init__(document, out) 78 79 def licenses_from_tree_helper(self, current, licenses): 80 if (isinstance(current, (document.LicenseConjunction, 81 document.LicenseDisjunction))): 82 self.licenses_from_tree_helper(current.license_1, licenses) 83 self.licenses_from_tree_helper(current.license_2, licenses) 84 else: 85 licenses.add(self.create_license_helper(current)) 86 87 def licenses_from_tree(self, tree): 88 """ 89 Traverse conjunctions and disjunctions like trees and return a 90 set of all licenses in it as nodes. 91 """ 92 # FIXME: this is unordered! 93 licenses = set() 94 self.licenses_from_tree_helper(tree, licenses) 95 return licenses 96 97 def create_conjunction_node(self, conjunction): 98 """ 99 Return a node representing a conjunction of licenses. 100 """ 101 node = BNode() 102 type_triple = (node, RDF.type, self.spdx_namespace.ConjunctiveLicenseSet) 103 self.graph.add(type_triple) 104 licenses = self.licenses_from_tree(conjunction) 105 for lic in licenses: 106 member_triple = (node, self.spdx_namespace.member, lic) 107 self.graph.add(member_triple) 108 return node 109 110 def create_disjunction_node(self, disjunction): 111 """ 112 Return a node representing a disjunction of licenses. 113 """ 114 node = BNode() 115 type_triple = (node, RDF.type, self.spdx_namespace.DisjunctiveLicenseSet) 116 self.graph.add(type_triple) 117 licenses = self.licenses_from_tree(disjunction) 118 for lic in licenses: 119 member_triple = (node, self.spdx_namespace.member, lic) 120 self.graph.add(member_triple) 121 return node 122 123 def create_license_helper(self, lic): 124 """ 125 Handle single(no conjunction/disjunction) licenses. 126 Return the created node. 127 """ 128 if isinstance(lic, document.ExtractedLicense): 129 return self.create_extracted_license(lic) 130 if lic.identifier.rstrip('+') in config.LICENSE_MAP: 131 return URIRef(lic.url) 132 else: 133 matches = [l for l in self.document.extracted_licenses if l.identifier == lic.identifier] 134 if len(matches) != 0: 135 return self.create_extracted_license(matches[0]) 136 else: 137 raise InvalidDocumentError('Missing extracted license: {0}'.format(lic.identifier)) 138 139 def create_extracted_license(self, lic): 140 """ 141 Handle extracted license. 142 Return the license node. 143 """ 144 licenses = list(self.graph.triples((None, self.spdx_namespace.licenseId, lic.identifier))) 145 if len(licenses) != 0: 146 return licenses[0][0] # return subject in first triple 147 else: 148 license_node = BNode() 149 type_triple = (license_node, RDF.type, self.spdx_namespace.ExtractedLicensingInfo) 150 self.graph.add(type_triple) 151 ident_triple = (license_node, self.spdx_namespace.licenseId, Literal(lic.identifier)) 152 self.graph.add(ident_triple) 153 text_triple = (license_node, self.spdx_namespace.extractedText, Literal(lic.text)) 154 self.graph.add(text_triple) 155 if lic.full_name is not None: 156 name_triple = (license_node, self.spdx_namespace.licenseName, self.to_special_value(lic.full_name)) 157 self.graph.add(name_triple) 158 for ref in lic.cross_ref: 159 triple = (license_node, RDFS.seeAlso, URIRef(ref)) 160 self.graph.add(triple) 161 if lic.comment is not None: 162 comment_triple = (license_node, RDFS.comment, Literal(lic.comment)) 163 self.graph.add(comment_triple) 164 return license_node 165 166 def create_license_node(self, lic): 167 """ 168 Return a node representing a license. 169 Could be a single license (extracted or part of license list.) or 170 a conjunction/disjunction of licenses. 171 """ 172 if isinstance(lic, document.LicenseConjunction): 173 return self.create_conjunction_node(lic) 174 elif isinstance(lic, document.LicenseDisjunction): 175 return self.create_disjunction_node(lic) 176 else: 177 return self.create_license_helper(lic) 178 179 def license_or_special(self, lic): 180 """ 181 Check for special values spdx:none and spdx:noassertion. 182 Return the term for the special value or the result of passing 183 license to create_license_node. 184 """ 185 if isinstance(lic, utils.NoAssert): 186 return self.spdx_namespace.noassertion 187 elif isinstance(lic, utils.SPDXNone): 188 return self.spdx_namespace.none 189 else: 190 return self.create_license_node(lic) 191 192 193class FileWriter(LicenseWriter): 194 """ 195 Write spdx.file.File 196 """ 197 FILE_TYPES = { 198 file.FileType.SOURCE: 'fileType_source', 199 file.FileType.OTHER: 'fileType_other', 200 file.FileType.BINARY: 'fileType_binary', 201 file.FileType.ARCHIVE: 'fileType_archive' 202 } 203 204 def __init__(self, document, out): 205 super(FileWriter, self).__init__(document, out) 206 207 def create_file_node(self, doc_file): 208 """ 209 Create a node for spdx.file. 210 """ 211 file_node = URIRef('http://www.spdx.org/files#{id}'.format( 212 id=str(doc_file.spdx_id))) 213 type_triple = (file_node, RDF.type, self.spdx_namespace.File) 214 self.graph.add(type_triple) 215 216 name_triple = (file_node, self.spdx_namespace.fileName, Literal(doc_file.name)) 217 self.graph.add(name_triple) 218 219 if doc_file.has_optional_field('comment'): 220 comment_triple = (file_node, RDFS.comment, Literal(doc_file.comment)) 221 self.graph.add(comment_triple) 222 223 if doc_file.has_optional_field('type'): 224 ftype = self.spdx_namespace[self.FILE_TYPES[doc_file.type]] 225 ftype_triple = (file_node, self.spdx_namespace.fileType, ftype) 226 self.graph.add(ftype_triple) 227 228 self.graph.add((file_node, self.spdx_namespace.checksum, self.create_checksum_node(doc_file.chk_sum))) 229 230 conc_lic_node = self.license_or_special(doc_file.conc_lics) 231 conc_lic_triple = (file_node, self.spdx_namespace.licenseConcluded, conc_lic_node) 232 self.graph.add(conc_lic_triple) 233 234 license_info_nodes = map(self.license_or_special, doc_file.licenses_in_file) 235 for lic in license_info_nodes: 236 triple = (file_node, self.spdx_namespace.licenseInfoInFile, lic) 237 self.graph.add(triple) 238 239 if doc_file.has_optional_field('license_comment'): 240 comment_triple = (file_node, self.spdx_namespace.licenseComments, Literal(doc_file.license_comment)) 241 self.graph.add(comment_triple) 242 243 cr_text_node = self.to_special_value(doc_file.copyright) 244 cr_text_triple = (file_node, self.spdx_namespace.copyrightText, cr_text_node) 245 self.graph.add(cr_text_triple) 246 247 if doc_file.has_optional_field('notice'): 248 notice_triple = (file_node, self.spdx_namespace.noticeText, doc_file.notice) 249 self.graph.add(notice_triple) 250 251 contrib_nodes = map(lambda c: Literal(c), doc_file.contributors) 252 contrib_triples = [(file_node, self.spdx_namespace.fileContributor, node) for node in contrib_nodes] 253 for triple in contrib_triples: 254 self.graph.add(triple) 255 256 return file_node 257 258 def files(self): 259 """ 260 Return list of file nodes. 261 """ 262 return map(self.create_file_node, self.document.files) 263 264 def add_file_dependencies_helper(self, doc_file): 265 """ 266 Handle dependencies for a single file. 267 - doc_file - instance of spdx.file.File. 268 """ 269 subj_triples = list(self.graph.triples((None, self.spdx_namespace.fileName, Literal(doc_file.name)))) 270 if len(subj_triples) != 1: 271 raise InvalidDocumentError('Could not find dependency subject {0}'.format(doc_file.name)) 272 subject_node = subj_triples[0][0] 273 for dependency in doc_file.dependencies: 274 dep_triples = list(self.graph.triples((None, self.spdx_namespace.fileName, Literal(dependency)))) 275 if len(dep_triples) == 1: 276 dep_node = dep_triples[0][0] 277 dep_triple = (subject_node, self.spdx_namespace.fileDependency, dep_node) 278 self.graph.add(dep_triple) 279 else: 280 print('Warning could not resolve file dependency {0} -> {1}'.format(doc_file.name, dependency)) 281 282 def add_file_dependencies(self): 283 """ 284 Add file dependencies to the graph. 285 Called after all files have been added. 286 """ 287 for doc_file in self.document.files: 288 self.add_file_dependencies_helper(doc_file) 289 290 291class SnippetWriter(LicenseWriter): 292 293 """ 294 Write spdx.snippet.Snippet 295 """ 296 297 def __init__(self, document, out): 298 super(SnippetWriter, self).__init__(document, out) 299 300 def create_snippet_node(self, snippet): 301 """ 302 Return a snippet node. 303 """ 304 snippet_node = URIRef('http://spdx.org/rdf/terms/Snippet#' + snippet.spdx_id) 305 type_triple = (snippet_node, RDF.type, self.spdx_namespace.Snippet) 306 self.graph.add(type_triple) 307 308 if snippet.has_optional_field('comment'): 309 comment_triple = (snippet_node, RDFS.comment, Literal(snippet.comment)) 310 self.graph.add(comment_triple) 311 312 if snippet.has_optional_field('name'): 313 name_triple = (snippet_node, self.spdx_namespace.name, Literal(snippet.name)) 314 self.graph.add(name_triple) 315 316 if snippet.has_optional_field('license_comment'): 317 lic_comment_triple = (snippet_node, self.spdx_namespace.licenseComments, 318 Literal(snippet.license_comment)) 319 self.graph.add(lic_comment_triple) 320 321 cr_text_node = self.to_special_value(snippet.copyright) 322 cr_text_triple = (snippet_node, self.spdx_namespace.copyrightText, cr_text_node) 323 self.graph.add(cr_text_triple) 324 325 snip_from_file_triple = (snippet_node, self.spdx_namespace.snippetFromFile, 326 Literal(snippet.snip_from_file_spdxid)) 327 self.graph.add(snip_from_file_triple) 328 329 conc_lic_node = self.license_or_special(snippet.conc_lics) 330 conc_lic_triple = ( 331 snippet_node, self.spdx_namespace.licenseConcluded, conc_lic_node) 332 self.graph.add(conc_lic_triple) 333 334 license_info_nodes = map(self.license_or_special, 335 snippet.licenses_in_snippet) 336 for lic in license_info_nodes: 337 triple = ( 338 snippet_node, self.spdx_namespace.licenseInfoInSnippet, lic) 339 self.graph.add(triple) 340 341 return snippet_node 342 343 def snippets(self): 344 """ 345 Return list of snippet nodes. 346 """ 347 return map(self.create_snippet_node, self.document.snippet) 348 349 350class ReviewInfoWriter(BaseWriter): 351 352 """ 353 Write spdx.review.Review 354 """ 355 356 def __init__(self, document, out): 357 super(ReviewInfoWriter, self).__init__(document, out) 358 359 def create_review_node(self, review): 360 """ 361 Return a review node. 362 """ 363 review_node = BNode() 364 type_triple = (review_node, RDF.type, self.spdx_namespace.Review) 365 self.graph.add(type_triple) 366 367 reviewer_node = Literal(review.reviewer.to_value()) 368 self.graph.add((review_node, self.spdx_namespace.reviewer, reviewer_node)) 369 reviewed_date_node = Literal(review.review_date_iso_format) 370 reviewed_triple = (review_node, self.spdx_namespace.reviewDate, reviewed_date_node) 371 self.graph.add(reviewed_triple) 372 if review.has_comment: 373 comment_node = Literal(review.comment) 374 comment_triple = (review_node, RDFS.comment, comment_node) 375 self.graph.add(comment_triple) 376 377 return review_node 378 379 def reviews(self): 380 "Returns a list of review nodes" 381 return map(self.create_review_node, self.document.reviews) 382 383 384class AnnotationInfoWriter(BaseWriter): 385 """ 386 Write spdx.annotation.Annotation 387 """ 388 389 def __init__(self, document, out): 390 super(AnnotationInfoWriter, self).__init__(document, out) 391 392 def create_annotation_node(self, annotation): 393 """ 394 Return an annotation node. 395 """ 396 annotation_node = URIRef(str(annotation.spdx_id)) 397 type_triple = (annotation_node, RDF.type, self.spdx_namespace.Annotation) 398 self.graph.add(type_triple) 399 400 annotator_node = Literal(annotation.annotator.to_value()) 401 self.graph.add((annotation_node, self.spdx_namespace.annotator, annotator_node)) 402 annotation_date_node = Literal(annotation.annotation_date_iso_format) 403 annotation_triple = (annotation_node, self.spdx_namespace.annotationDate, annotation_date_node) 404 self.graph.add(annotation_triple) 405 if annotation.has_comment: 406 comment_node = Literal(annotation.comment) 407 comment_triple = (annotation_node, RDFS.comment, comment_node) 408 self.graph.add(comment_triple) 409 annotation_type_node = Literal(annotation.annotation_type) 410 annotation_type_triple = (annotation_node, self.spdx_namespace.annotationType, annotation_type_node) 411 self.graph.add(annotation_type_triple) 412 413 return annotation_node 414 415 def annotations(self): 416 """ 417 Return a list of annotation nodes 418 """ 419 return map(self.create_annotation_node, self.document.annotations) 420 421 422class CreationInfoWriter(BaseWriter): 423 424 """ 425 Write class spdx.creationinfo.CreationInfo 426 """ 427 428 def __init__(self, document, out): 429 super(CreationInfoWriter, self).__init__(document, out) 430 431 def creators(self): 432 """ 433 Return a list of creator nodes. 434 Note: Does not add anything to the graph. 435 """ 436 return map(lambda c: Literal(c.to_value()), self.document.creation_info.creators) 437 438 def create_creation_info(self): 439 """ 440 Add and return a creation info node to graph 441 """ 442 ci_node = BNode() 443 # Type property 444 type_triple = (ci_node, RDF.type, self.spdx_namespace.CreationInfo) 445 self.graph.add(type_triple) 446 447 created_date = Literal(self.document.creation_info.created_iso_format) 448 created_triple = (ci_node, self.spdx_namespace.created, created_date) 449 self.graph.add(created_triple) 450 451 creators = self.creators() 452 for creator in creators: 453 self.graph.add((ci_node, self.spdx_namespace.creator, creator)) 454 455 if self.document.creation_info.has_comment: 456 comment_node = Literal(self.document.creation_info.comment) 457 comment_triple = (ci_node, RDFS.comment, comment_node) 458 self.graph.add(comment_triple) 459 460 return ci_node 461 462 463class ExternalDocumentRefWriter(BaseWriter): 464 """ 465 Write class spdx.external_document_ref.ExternalDocumentRef 466 """ 467 468 def __init__(self, document, out): 469 super(ExternalDocumentRefWriter, self).__init__(document, out) 470 471 def create_external_document_ref_node(self, ext_document_references): 472 """ 473 Add and return a creation info node to graph 474 """ 475 ext_doc_ref_node = BNode() 476 type_triple = (ext_doc_ref_node, RDF.type, self.spdx_namespace.ExternalDocumentRef) 477 self.graph.add(type_triple) 478 479 ext_doc_id = Literal( 480 ext_document_references.external_document_id) 481 ext_doc_id_triple = ( 482 ext_doc_ref_node, self.spdx_namespace.externalDocumentId, ext_doc_id) 483 self.graph.add(ext_doc_id_triple) 484 485 doc_uri = Literal( 486 ext_document_references.spdx_document_uri) 487 doc_uri_triple = ( 488 ext_doc_ref_node, self.spdx_namespace.spdxDocument, doc_uri) 489 self.graph.add(doc_uri_triple) 490 491 checksum_node = self.create_checksum_node( 492 ext_document_references.check_sum) 493 self.graph.add( 494 (ext_doc_ref_node, self.spdx_namespace.checksum, checksum_node)) 495 496 return ext_doc_ref_node 497 498 def ext_doc_refs(self): 499 """ 500 Return a list of review nodes 501 """ 502 return map(self.create_external_document_ref_node, 503 self.document.ext_document_references) 504 505 506class PackageWriter(LicenseWriter): 507 508 """ 509 Write spdx.package.Package 510 """ 511 512 def __init__(self, document, out): 513 super(PackageWriter, self).__init__(document, out) 514 515 def package_verif_node(self, package): 516 """ 517 Return a node representing package verification code. 518 """ 519 verif_node = BNode() 520 type_triple = (verif_node, RDF.type, self.spdx_namespace.PackageVerificationCode) 521 self.graph.add(type_triple) 522 value_triple = (verif_node, self.spdx_namespace.packageVerificationCodeValue, Literal(package.verif_code)) 523 self.graph.add(value_triple) 524 excl_file_nodes = map( 525 lambda excl: Literal(excl), package.verif_exc_files) 526 excl_predicate = self.spdx_namespace.packageVerificationCodeExcludedFile 527 excl_file_triples = [(verif_node, excl_predicate, xcl_file) for xcl_file in excl_file_nodes] 528 for trp in excl_file_triples: 529 self.graph.add(trp) 530 return verif_node 531 532 def handle_package_literal_optional(self, package, package_node, predicate, field): 533 """ 534 Check if optional field is set. 535 If so it adds the triple (package_node, predicate, $) to the graph. 536 Where $ is a literal or special value term of the value of the field. 537 """ 538 if package.has_optional_field(field): 539 value = getattr(package, field, None) 540 value_node = self.to_special_value(value) 541 triple = (package_node, predicate, value_node) 542 self.graph.add(triple) 543 544 def handle_pkg_optional_fields(self, package, package_node): 545 """ 546 Write package optional fields. 547 """ 548 self.handle_package_literal_optional(package, package_node, self.spdx_namespace.versionInfo, 'version') 549 self.handle_package_literal_optional(package, package_node, self.spdx_namespace.packageFileName, 'file_name') 550 self.handle_package_literal_optional(package, package_node, self.spdx_namespace.supplier, 'supplier') 551 self.handle_package_literal_optional(package, package_node, self.spdx_namespace.originator, 'originator') 552 self.handle_package_literal_optional(package, package_node, self.spdx_namespace.sourceInfo, 'source_info') 553 self.handle_package_literal_optional(package, package_node, self.spdx_namespace.licenseComments, 'license_comment') 554 self.handle_package_literal_optional(package, package_node, self.spdx_namespace.summary, 'summary') 555 self.handle_package_literal_optional(package, package_node, self.spdx_namespace.description, 'description') 556 self.handle_package_literal_optional(package, package_node, self.spdx_namespace.comment, 'comment') 557 self.handle_package_literal_optional(package, package_node, self.spdx_namespace.filesAnalyzed, 'files_analyzed') 558 559 560 if package.has_optional_field('check_sum'): 561 checksum_node = self.create_checksum_node(package.check_sum) 562 self.graph.add((package_node, self.spdx_namespace.checksum, checksum_node)) 563 564 if package.has_optional_field('homepage'): 565 homepage_node = URIRef(self.to_special_value(package.homepage)) 566 homepage_triple = (package_node, self.doap_namespace.homepage, homepage_node) 567 self.graph.add(homepage_triple) 568 569 def create_package_node(self, package): 570 """ 571 Return a Node representing the package. 572 Files must have been added to the graph before this method is called. 573 """ 574 package_node = URIRef('http://www.spdx.org/tools#SPDXRef-Package') 575 type_triple = (package_node, RDF.type, self.spdx_namespace.Package) 576 self.graph.add(type_triple) 577 # Package SPDXID 578 if package.spdx_id: 579 pkg_spdx_id = URIRef(package.spdx_id) 580 pkg_spdx_id_triple = (package_node, self.spdx_namespace.Package, 581 pkg_spdx_id) 582 self.graph.add(pkg_spdx_id_triple) 583 # Handle optional fields: 584 self.handle_pkg_optional_fields(package, package_node) 585 # package name 586 name_triple = (package_node, self.spdx_namespace.name, Literal(package.name)) 587 self.graph.add(name_triple) 588 # Package download location 589 down_loc_node = (package_node, self.spdx_namespace.downloadLocation, self.to_special_value(package.download_location)) 590 self.graph.add(down_loc_node) 591 # Handle package verification 592 verif_node = self.package_verif_node(package) 593 verif_triple = (package_node, self.spdx_namespace.packageVerificationCode, verif_node) 594 self.graph.add(verif_triple) 595 # Handle concluded license 596 conc_lic_node = self.license_or_special(package.conc_lics) 597 conc_lic_triple = (package_node, self.spdx_namespace.licenseConcluded, conc_lic_node) 598 self.graph.add(conc_lic_triple) 599 # Handle declared license 600 decl_lic_node = self.license_or_special(package.license_declared) 601 decl_lic_triple = (package_node, self.spdx_namespace.licenseDeclared, decl_lic_node) 602 self.graph.add(decl_lic_triple) 603 # Package licenses from files 604 licenses_from_files_nodes = map(lambda el: self.license_or_special(el), package.licenses_from_files) 605 lic_from_files_predicate = self.spdx_namespace.licenseInfoFromFiles 606 lic_from_files_triples = [(package_node, lic_from_files_predicate, node) for node in licenses_from_files_nodes] 607 for triple in lic_from_files_triples: 608 self.graph.add(triple) 609 # Copyright Text 610 cr_text_node = self.to_special_value(package.cr_text) 611 cr_text_triple = (package_node, self.spdx_namespace.copyrightText, cr_text_node) 612 self.graph.add(cr_text_triple) 613 # Handle files 614 self.handle_package_has_file(package, package_node) 615 return package_node 616 617 def packages(self): 618 """ 619 Return a node that represents the package in the graph. 620 Call this function to write package info. 621 """ 622 # TODO: In the future this may be a list to support SPDX 2.0 623 return self.create_package_node(self.document.package) 624 625 def handle_package_has_file_helper(self, pkg_file): 626 """ 627 Return node representing pkg_file 628 pkg_file should be instance of spdx.file. 629 """ 630 nodes = list(self.graph.triples((None, self.spdx_namespace.fileName, Literal(pkg_file.name)))) 631 if len(nodes) == 1: 632 return nodes[0][0] 633 else: 634 raise InvalidDocumentError('handle_package_has_file_helper could not' + 635 ' find file node for file: {0}'.format(pkg_file.name)) 636 637 def handle_package_has_file(self, package, package_node): 638 """ 639 Add hasFile triples to graph. 640 Must be called after files have been added. 641 """ 642 file_nodes = map(self.handle_package_has_file_helper, package.files) 643 triples = [(package_node, self.spdx_namespace.hasFile, node) for node in file_nodes] 644 for triple in triples: 645 self.graph.add(triple) 646 647 648class PackageExternalRefWriter(BaseWriter): 649 """ 650 Write class spdx.package.ExternalPackageRef 651 """ 652 653 def __init__(self, document, out): 654 super(PackageExternalRefWriter, self).__init__(document, out) 655 656 def create_package_external_ref_node(self, pkg_ext_refs): 657 """ 658 Add and return an external package reference node to graph. 659 """ 660 pkg_ext_ref_node = BNode() 661 pkg_ext_ref_triple = (pkg_ext_ref_node, RDF.type, self.spdx_namespace.ExternalRef) 662 self.graph.add(pkg_ext_ref_triple) 663 664 pkg_ext_ref_category = Literal(pkg_ext_refs.category) 665 pkg_ext_ref_category_triple = ( 666 pkg_ext_ref_node, self.spdx_namespace.referenceCategory, pkg_ext_ref_category) 667 self.graph.add(pkg_ext_ref_category_triple) 668 669 pkg_ext_ref_type = Literal(pkg_ext_refs.pkg_ext_ref_type) 670 pkg_ext_ref_type_triple = ( 671 pkg_ext_ref_node, self.spdx_namespace.referenceType, pkg_ext_ref_type) 672 self.graph.add(pkg_ext_ref_type_triple) 673 674 pkg_ext_ref_locator = Literal(pkg_ext_refs.locator) 675 pkg_ext_ref_locator_triple = ( 676 pkg_ext_ref_node, self.spdx_namespace.referenceLocator, pkg_ext_ref_locator) 677 self.graph.add(pkg_ext_ref_locator_triple) 678 679 pkg_ext_ref_comment = Literal(pkg_ext_refs.comment) 680 pkg_ext_ref_comment_triple = ( 681 pkg_ext_ref_node, RDFS.comment, pkg_ext_ref_comment) 682 self.graph.add(pkg_ext_ref_comment_triple) 683 684 return pkg_ext_ref_node 685 686 def pkg_ext_refs(self): 687 """ 688 Return a list of package external references. 689 """ 690 return map(self.create_package_external_ref_node, 691 self.document.package.pkg_ext_refs) 692 693 694class Writer(CreationInfoWriter, ReviewInfoWriter, FileWriter, PackageWriter, 695 PackageExternalRefWriter, ExternalDocumentRefWriter, AnnotationInfoWriter, 696 SnippetWriter): 697 """ 698 Warpper for other writers to write all fields of spdx.document.Document 699 Call `write()` to start writing. 700 """ 701 702 def __init__(self, document, out): 703 """ 704 - document is spdx.document instance that will be written. 705 - out is a file-like object that will be written to. 706 """ 707 super(Writer, self).__init__(document, out) 708 709 def create_doc(self): 710 """ 711 Add and return the root document node to graph. 712 """ 713 doc_node = URIRef('http://www.spdx.org/tools#SPDXRef-DOCUMENT') 714 # Doc type 715 self.graph.add((doc_node, RDF.type, self.spdx_namespace.SpdxDocument)) 716 # Version 717 vers_literal = Literal(str(self.document.version)) 718 self.graph.add((doc_node, self.spdx_namespace.specVersion, vers_literal)) 719 # Data license 720 data_lics = URIRef(self.document.data_license.url) 721 self.graph.add((doc_node, self.spdx_namespace.dataLicense, data_lics)) 722 if self.document.name: 723 doc_name = URIRef(self.document.name) 724 self.graph.add((doc_node, self.spdx_namespace.name, doc_name)) 725 return doc_node 726 727 def write(self): 728 doc_node = self.create_doc() 729 # Add creation info 730 creation_info_node = self.create_creation_info() 731 ci_triple = (doc_node, self.spdx_namespace.creationInfo, creation_info_node) 732 self.graph.add(ci_triple) 733 # Add review info 734 review_nodes = self.reviews() 735 for review in review_nodes: 736 self.graph.add((doc_node, self.spdx_namespace.reviewed, review)) 737 # Add external document references info 738 ext_doc_ref_nodes = self.ext_doc_refs() 739 for ext_doc_ref in ext_doc_ref_nodes: 740 ext_doc_ref_triple = (doc_node, 741 self.spdx_namespace.externalDocumentRef, 742 ext_doc_ref) 743 self.graph.add(ext_doc_ref_triple) 744 # Add extracted licenses 745 licenses = map( 746 self.create_extracted_license, self.document.extracted_licenses) 747 for lic in licenses: 748 self.graph.add((doc_node, self.spdx_namespace.hasExtractedLicensingInfo, lic)) 749 # Add files 750 files = self.files() 751 for file_node in files: 752 self.graph.add((doc_node, self.spdx_namespace.referencesFile, file_node)) 753 self.add_file_dependencies() 754 # Add package 755 package_node = self.packages() 756 package_triple = (doc_node, self.spdx_namespace.describesPackage, package_node) 757 self.graph.add(package_triple) 758 # Add snippet 759 snippet_nodes = self.snippets() 760 for snippet in snippet_nodes: 761 self.graph.add((doc_node, self.spdx_namespace.Snippet, snippet)) 762 763 # normalize the graph to ensure that the sort order is stable 764 self.graph = to_isomorphic(self.graph) 765 766 # Write file 767 self.graph.serialize(self.out, 'pretty-xml', encoding='utf-8') 768 769 770def write_document(document, out, validate=True): 771 """ 772 Write an SPDX RDF document. 773 - document - spdx.document instance. 774 - out - file like object that will be written to. 775 Optionally `validate` the document before writing and raise 776 InvalidDocumentError if document.validate returns False. 777 """ 778 779 if validate: 780 messages = [] 781 messages = document.validate(messages) 782 if messages: 783 raise InvalidDocumentError(messages) 784 785 writer = Writer(document, out) 786 writer.write() 787