Repository: spdx/tools-python

Issue:
Duplicate extracted licenses when parsing from RDF
The field Document.extracted_licenses contains duplicate ExtractedLicense objects when they are parsed from RDF files.

It can be noticed by running [parse_rdf.py](https://github.com/spdx/tools-python/blob/master/examples/parse_rdf.py). 
Input: [SPDXRdfExample.rdf](https://github.com/spdx/tools-python/blob/master/data/SPDXRdfExample.rdf)
Output:
```
doc comment: This is a sample spreadsheet
Creators:
        Person: Gary O'Neall
        Tool: SourceAuditor-V1.2
        Organization: Source Auditor Inc.
Document review information:
        Reviewer: Person: Suzanne Reviewer
        Date: 2011-03-13 00:00:00
        Comment: Another example reviewer.
        Reviewer: Person: Joe Reviewer
        Date: 2010-02-10 00:00:00
        Comment: This is just an example.  Some of the non-standard licenses look like they are actually BSD 3 clause licenses
Creation comment: This is an example of an SPDX spreadsheet format
Package Name: SPDX Translator
Package Version: Version 0.9.2
Package Download Location: http://www.spdx.org/tools
Package Homepage: None
Package Checksum: 2fd4e1c67a2d28fced849ee1bb76e7391b93eb12
Package verification code: 4e3211c67a2d28fced849ee1bb76e7391b93feba
Package excluded from verif: SpdxTranslatorSpdx.txt,SpdxTranslatorSpdx.rdf
Package license concluded: LicenseRef-4 AND LicenseRef-2 AND Apache-1.0 AND LicenseRef-3 AND LicenseRef-1 AND Apache-2.0 AND MPL-1.1
Package license declared: MPL-1.1 AND Apache-2.0 AND LicenseRef-3 AND LicenseRef-2 AND LicenseRef-4 AND LicenseRef-1
Package licenses from files:
        LicenseRef-1
        LicenseRef-3
        Apache-1.0
        MPL-1.1
        LicenseRef-4
        LicenseRef-2
        Apache-2.0
Package Copyright text:  Copyright 2010, 2011 Source Auditor Inc.
Package summary: SPDX Translator utility
Package description: This utility translates and SPDX RDF XML document to a spreadsheet, translates a spreadsheet to an SPDX RDF XML document and translates an SPDX RDFa document to an SPDX RDF XML document.
Package Files:
        File name: Jenna-2.6.3/jena-2.6.3-sources.jar
        File type: ARCHIVE
        File Checksum: 3ab4e1c67a2d28fced849ee1bb76e7391b93f125
        File license concluded: LicenseRef-1
        File license info in file: LicenseRef-1
        File artifact of project name: Jena
        File name: src/org/spdx/parser/DOAPProject.java
        File type: SOURCE
        File Checksum: 2fd4e1c67a2d28fced849ee1bb76e7391b93eb12
        File license concluded: Apache-2.0
        File license info in file: Apache-2.0
        File artifact of project name:
Document Extracted licenses:
        Identifier: LicenseRef-4
        Name: None
        Identifier: LicenseRef-2
        Name: None
        Identifier: LicenseRef-3
        Name: CyberNeko License
        Identifier: LicenseRef-1
        Name: None
        Identifier: LicenseRef-3
        Name: CyberNeko License
        Identifier: LicenseRef-2
        Name: None
        Identifier: LicenseRef-4
        Name: None
        Identifier: LicenseRef-1
        Name: None
Annotations:
        Annotator: Person: Jim Reviewer
        Annotation Date: 2012-06-13 00:00:00
        Annotation Comment: This is just an example. Some of the non-standard licenses look like they are actually BSD 3 clause licenses
        Annotation Type: REVIEW
        Annotation SPDX Identifier: https://spdx.org/spdxdocs/spdx-example-444504E0-4F89-41D3-9A0C-0305E82C3301#SPDXRef-45
```

Comments:

Patch:
diff --git a/spdx/parsers/rdf.py b/spdx/parsers/rdf.py
index 7aa98a5..568aa06 100644
--- a/spdx/parsers/rdf.py
+++ b/spdx/parsers/rdf.py
@@ -266,12 +266,7 @@ class LicenseParser(BaseParser):
         for _, _, lics_member in self.graph.triples(
             (lics_set, self.spdx_namespace['member'], None)):
             try:
-                if (lics_member, RDF.type, self.spdx_namespace['ExtractedLicensingInfo']) in self.graph:
-                    lics = self.handle_extracted_license(lics_member)
-                    if lics is not None:
-                        licenses.append(lics)
-                else:
-                    licenses.append(self.handle_lics(lics_member))
+                licenses.append(self.handle_lics(lics_member))
             except CardinalityError:
                 self.value_error('LICS_LIST_MEMBER', lics_member)
                 break
@@ -935,6 +930,9 @@ class Parser(PackageParser, FileParser, SnippetParser, ReviewParser, AnnotationP
 
         for s, _p, o in self.graph.triples((None, RDF.type, self.spdx_namespace['CreationInfo'])):
             self.parse_creation_info(s)
+        
+        for s, _p, o in self.graph.triples((None, None, self.spdx_namespace['ExtractedLicensingInfo'])):
+            self.handle_extracted_license(s)
 
         for s, _p, o in self.graph.triples((None, RDF.type, self.spdx_namespace['Package'])):
             self.parse_package(s)


Test patch:
diff --git a/tests/test_rdf_parser.py b/tests/test_rdf_parser.py
new file mode 100644
index 0000000..c212837
--- /dev/null
+++ b/tests/test_rdf_parser.py
@@ -0,0 +1,54 @@
+import unittest
+import re
+from unittest import TestCase
+from spdx.parsers.rdf import Parser
+from spdx.parsers.rdfbuilders import Builder
+from spdx.parsers.loggers import StandardLogger
+from tests import utils_test
+
+class TestParser(TestCase):
+
+    def test_extracted_licenses(self):
+        parser = Parser(Builder(), StandardLogger())
+        test_file = utils_test.get_test_loc('../../data/SPDXRdfExample.rdf', test_data_dir=utils_test.test_data_dir)
+        with open(test_file, 'r') as file:
+            document, _ = parser.parse(file)
+        assert len(document.extracted_licenses) == 4
+        # It is needed to sort the list because the order changes when parsing
+        licenses = sorted(document.extracted_licenses)
+        assert licenses[0].identifier.toPython() == 'LicenseRef-1'
+        assert licenses[1].identifier.toPython() == 'LicenseRef-2'
+        assert licenses[2].identifier.toPython() == 'LicenseRef-3'
+        assert licenses[3].identifier.toPython() == 'LicenseRef-4'
+    
+    def test_pkg_lic_decl(self):
+        parser = Parser(Builder(), StandardLogger())
+        test_file = utils_test.get_test_loc('../../data/SPDXRdfExample.rdf', test_data_dir=utils_test.test_data_dir)
+        with open(test_file, 'r') as file:
+            document, _ = parser.parse(file)
+        # It is needed to ckeck it as sorted lists because the order changes when parsing
+        lic_expected = ['Apache-2.0', 'LicenseRef-1', 'LicenseRef-2', 'LicenseRef-3', 'LicenseRef-4', 'MPL-1.1']
+        lic_result = sorted(document.package.license_declared.identifier.split(' AND '))
+        assert lic_result == lic_expected
+    
+    def test_pkg_lic_conc(self):
+        parser = Parser(Builder(), StandardLogger())
+        test_file = utils_test.get_test_loc('../../data/SPDXRdfExample.rdf', test_data_dir=utils_test.test_data_dir)
+        with open(test_file, 'r') as file:
+            document, _ = parser.parse(file)
+        # It is needed to ckeck it as sorted lists because the order changes when parsing
+        lic_expected = ['Apache-1.0', 'Apache-2.0', 'LicenseRef-1', 'LicenseRef-2', 'LicenseRef-3', 'LicenseRef-4', 'MPL-1.1']
+        lic_result = sorted(document.package.conc_lics.identifier.split(' AND '))
+        assert lic_result == lic_expected
+    
+    def test_file_lic_conc(self):
+        parser = Parser(Builder(), StandardLogger())
+        test_file = utils_test.get_test_loc('../../data/SPDXRdfExample.rdf', test_data_dir=utils_test.test_data_dir)
+        with open(test_file, 'r') as file:
+            document, _ = parser.parse(file)
+        files = sorted(document.package.files)
+        assert files[0].conc_lics.identifier.toPython() == 'LicenseRef-1'
+        assert files[1].conc_lics.identifier == 'Apache-2.0'
+
+if __name__ == '__main__':
+    unittest.main()