Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-lib4sbom for openSUSE:Factory 
checked in at 2026-04-20 16:11:38
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-lib4sbom (Old)
 and      /work/SRC/openSUSE:Factory/.python-lib4sbom.new.11940 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-lib4sbom"

Mon Apr 20 16:11:38 2026 rev:3 rq:1348078 version:0.10.4

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-lib4sbom/python-lib4sbom.changes  
2026-04-12 18:47:50.915055654 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-lib4sbom.new.11940/python-lib4sbom.changes   
    2026-04-20 16:11:54.894678666 +0200
@@ -1,0 +2,10 @@
+Sun Apr 19 20:08:25 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 0.10.4:
+  * feat: add license exception handling
+  * fix: handle orlater (fixes #88)
+  * fix: SPDX3 license with exception (fixes #89)
+  * fix: SPDX handling of OPERATING-SYSTEM for JSON (fixes #78)
+  * test: add license test suite
+
+-------------------------------------------------------------------

Old:
----
  lib4sbom-0.10.3.tar.gz

New:
----
  lib4sbom-0.10.4.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-lib4sbom.spec ++++++
--- /var/tmp/diff_new_pack.3tiwET/_old  2026-04-20 16:11:55.406699748 +0200
+++ /var/tmp/diff_new_pack.3tiwET/_new  2026-04-20 16:11:55.406699748 +0200
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-lib4sbom
-Version:        0.10.3
+Version:        0.10.4
 Release:        0
 Summary:        Library to ingest and generate SBOMs
 License:        Apache-2.0

++++++ lib4sbom-0.10.3.tar.gz -> lib4sbom-0.10.4.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib4sbom-0.10.3/lib4sbom/license.py 
new/lib4sbom-0.10.4/lib4sbom/license.py
--- old/lib4sbom-0.10.3/lib4sbom/license.py     2026-03-31 13:07:41.000000000 
+0200
+++ new/lib4sbom-0.10.4/lib4sbom/license.py     2026-04-17 14:03:35.000000000 
+0200
@@ -18,6 +18,13 @@
         if self._check_file(license_path):
             licfile = open(license_path, "r", encoding="utf-8")
             self.licenses = json.load(licfile)
+        self.exceptions = {}
+        exception_path = os.path.join(
+            license_dir, "license_data", "spdx_exceptions.json"
+        )
+        if self._check_file(exception_path):
+            exception_file = open(exception_path, "r", encoding="utf-8")
+            self.exceptions = json.load(exception_file)
         # Set up list of license synonyms
         synonym_file = os.path.join(license_dir, "license_data", 
"license_synonyms.txt")
         self.license_synonym = {}
@@ -81,15 +88,35 @@
         # Deprecated license ids are still valid
         if self.deprecated(license):
             return license
+        if self.license_exception(license):
+            # Check valid exception included
+            return self.exception_processing(license)
+        return self._validate_license(license)
+
+    def _handle_later(self, license):
+        # Some licences have + appended to indicate 'or-later':
+        return license[:-1] if license[-1] == "+" else license
+
+    def orlater(self, license):
+        return self._handle_later(license) != license
+
+    def _validate_license(self, license):
+        license_id = self.check_synonym(license)
+        if license_id is None:
+            license_id = license
+        if self.orlater(license):
+            license_id = self._handle_later(license)
+        extra = "+" if self.orlater(license) else ""
         for lic in self.get_license_list():
             # Comparisons ignore case of provided license text
-            if lic["licenseId"].lower() == license.lower():
-                return lic["licenseId"]
-            elif lic["name"].lower() == license.lower():
-                return lic["licenseId"]
+            if lic["licenseId"].lower() == license_id.lower():
+                return f'{lic["licenseId"]}{extra}'
+            elif lic["name"].lower() == license_id.lower():
+                return f'{lic["licenseId"]}{extra}'
         return self.DEFAULT_LICENSE
 
     def get_license_text(self, license_id):
+        license_id = self._handle_later(license_id)
         license_text = ""
         filename = f"{self.license_text_path}/{license_id.lower()}.html"
         # check filename exists
@@ -102,6 +129,7 @@
     def get_license_name(self, license_id):
         # Assume that license_id is a valid SPDX id
         if license_id != self.DEFAULT_LICENSE:
+            license_id = self._handle_later(license_id)
             for lic in self.get_license_list():
                 if lic["licenseId"] == license_id:
                     return lic["name"]
@@ -109,6 +137,10 @@
 
     def get_license_url(self, license_id):
         # Assume that license_id is a valid SPDX id
+        if self.license_exception(license_id):
+            # Extract license
+            license_id = license_id.split(" ")[0]
+        license_id = self._handle_later(license_id)
         if license_id != self.DEFAULT_LICENSE:
             for lic in self.get_license_list():
                 # License URL is in the seeAlso field.
@@ -120,6 +152,7 @@
     def osi_approved(self, license_id):
         # Assume that license_id is a valid SPDX id
         if license_id != self.DEFAULT_LICENSE:
+            license_id = self._handle_later(license_id)
             for lic in self.get_license_list():
                 if lic["licenseId"] == license_id:
                     return lic["isOsiApproved"]
@@ -128,6 +161,7 @@
     def deprecated(self, license_id):
         # Assume that license_id is a valid SPDX id
         if license_id != self.DEFAULT_LICENSE:
+            license_id = self._handle_later(license_id)
             for lic in self.get_license_list():
                 if lic["licenseId"] == license_id and 
lic["isDeprecatedLicenseId"]:
                     return True
@@ -205,11 +239,72 @@
         # Determine if license expression contains multiple elements
         return len(self._expression_split(expression)) > 1
 
+    def _update_exception(self, license):
+        return license.replace(" with ", " WITH ").replace(" With ", " WITH ")
+
+    def license_exception(self, license):
+        return " WITH " in self._update_exception(license)
+
+    def _validate_exception(self, exception_id):
+        for exception in self.exceptions.get("exceptions", []):
+            # Comparisons ignore case of provided exception text
+            if exception["licenseExceptionId"].lower() == exception_id.lower():
+                return exception["licenseExceptionId"]
+            elif exception["name"].lower() == exception_id.lower():
+                return exception["licenseExceptionId"]
+        return None
+
+    def get_license_from_exception(self, license):
+        if self.license_exception(license):
+            updated_license = self._update_exception(license)
+            license_id = self._validate_license(updated_license.split(" WITH 
")[0])
+            if license_id != self.DEFAULT_LICENSE:
+                return license_id
+        return None
+
+    def exception_processing(self, license):
+        if self.license_exception(license):
+            # Assume format is <licence> WITH <exception>
+            # Check license and then exception
+            updated_license = self._update_exception(license)
+            valid_license = self._validate_license(updated_license.split(" 
WITH ")[0])
+            if valid_license == self.DEFAULT_LICENSE:
+                return valid_license
+            # There is a valid license, so now check exception
+            exception_id = updated_license.split(" WITH ")[1]
+            valid_exception = self._validate_exception(exception_id)
+            if valid_exception is not None:
+                return f"{valid_license} WITH {valid_exception}"
+            return self.DEFAULT_LICENSE
+        return None
+
+    def get_exception(self, license_with_exception):
+        if self.license_exception(license_with_exception):
+            return self._validate_exception(
+                self._update_exception(license_with_exception).split(" WITH 
")[1]
+            )
+        return None
+
+    def get_exception_text(self, exception_id):
+        if self._validate_exception(exception_id):
+            return self.get_license_text(exception_id)
+        return ""
+
+    def get_exception_url(self, exception_id):
+        # Assume that exception_id is a valid id
+        for exception in self.exceptions.get("exceptions", []):
+            # Comparisons ignore case of provided exception text
+            if exception["licenseExceptionId"] == exception_id:
+                return exception["seeAlso"][0]
+        return None
+
     def get_license_type(self, license):
         # Default is UNKNOWN
-        license_id = self.check_synonym(license)
-        if license_id is None:
-            license_id = license
+        license_id = self.find_license_id(license)
+        license_id = self._handle_later(license_id)
+        if self.license_exception(license):
+            # Extract licence component is exception detected
+            license_id = self._validate_license(license.split(" ")[0])
         return self.license_type.get(license_id.upper(), "unknown").upper()
 
     def get_license_category(self, license_list):
@@ -245,4 +340,8 @@
         return False
 
     def expression_license_list(self, expression):
-        return self._expression_split(expression)
+        license_list = []
+        for lic in self._expression_split(expression):
+            license_list.append(self.find_license(lic))
+        return license_list
+        # return self._expression_split(expression)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/lib4sbom-0.10.3/lib4sbom/license_data/spdx_exceptions.json 
new/lib4sbom-0.10.4/lib4sbom/license_data/spdx_exceptions.json
--- old/lib4sbom-0.10.3/lib4sbom/license_data/spdx_exceptions.json      
1970-01-01 01:00:00.000000000 +0100
+++ new/lib4sbom-0.10.4/lib4sbom/license_data/spdx_exceptions.json      
2026-04-17 14:03:35.000000000 +0200
@@ -0,0 +1,971 @@
+{
+  "licenseListVersion": "3.28.0",
+  "exceptions": [
+    {
+      "reference": "https://spdx.org/licenses/389-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/389-exception.json";,
+      "referenceNumber": 49,
+      "name": "389 Directory Server Exception",
+      "licenseExceptionId": "389-exception",
+      "seeAlso": [
+        "http://directory.fedoraproject.org/wiki/GPL_Exception_License_Text";,
+        
"https://web.archive.org/web/20080828121337/http://directory.fedoraproject.org/wiki/GPL_Exception_License_Text";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/Asterisk-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/Asterisk-exception.json";,
+      "referenceNumber": 3,
+      "name": "Asterisk exception",
+      "licenseExceptionId": "Asterisk-exception",
+      "seeAlso": [
+        
"https://github.com/asterisk/libpri/blob/7f91151e6bd10957c746c031c1f4a030e8146e9a/pri.c#L22";,
+        
"https://github.com/asterisk/libss7/blob/03e81bcd0d28ff25d4c77c78351ddadc82ff5c3f/ss7.c#L24";
+      ]
+    },
+    {
+      "reference": 
"https://spdx.org/licenses/Asterisk-linking-protocols-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": 
"https://spdx.org/licenses/Asterisk-linking-protocols-exception.json";,
+      "referenceNumber": 4,
+      "name": "Asterisk linking protocols exception",
+      "licenseExceptionId": "Asterisk-linking-protocols-exception",
+      "seeAlso": [
+        
"https://github.com/asterisk/asterisk/blob/115d7c01e32ccf4566a99e9d74e2b88830985a0b/LICENSE#L27";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/Autoconf-exception-2.0.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/Autoconf-exception-2.0.json";,
+      "referenceNumber": 30,
+      "name": "Autoconf exception 2.0",
+      "licenseExceptionId": "Autoconf-exception-2.0",
+      "seeAlso": [
+        "http://ac-archive.sourceforge.net/doc/copyright.html";,
+        "http://ftp.gnu.org/gnu/autoconf/autoconf-2.59.tar.gz";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/Autoconf-exception-3.0.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/Autoconf-exception-3.0.json";,
+      "referenceNumber": 51,
+      "name": "Autoconf exception 3.0",
+      "licenseExceptionId": "Autoconf-exception-3.0",
+      "seeAlso": [
+        "http://www.gnu.org/licenses/autoconf-exception-3.0.html";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/Autoconf-exception-generic.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": 
"https://spdx.org/licenses/Autoconf-exception-generic.json";,
+      "referenceNumber": 28,
+      "name": "Autoconf generic exception",
+      "licenseExceptionId": "Autoconf-exception-generic",
+      "seeAlso": [
+        "https://launchpad.net/ubuntu/precise/+source/xmltooling/+copyright";,
+        
"https://tracker.debian.org/media/packages/s/sipwitch/copyright-1.9.15-3";,
+        
"https://opensource.apple.com/source/launchd/launchd-258.1/launchd/compile.auto.html";,
+        
"https://git.savannah.gnu.org/gitweb/?p\u003dgnulib.git;a\u003dblob;f\u003dgnulib-tool;h\u003d029a8cf377ad8d8f2d9e54061bf2f20496ad2eef;hb\u003d73c74ba0197e6566da6882c87b1adee63e24d75c#l407";
+      ]
+    },
+    {
+      "reference": 
"https://spdx.org/licenses/Autoconf-exception-generic-3.0.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": 
"https://spdx.org/licenses/Autoconf-exception-generic-3.0.json";,
+      "referenceNumber": 37,
+      "name": "Autoconf generic exception for GPL-3.0",
+      "licenseExceptionId": "Autoconf-exception-generic-3.0",
+      "seeAlso": [
+        
"https://src.fedoraproject.org/rpms/redhat-rpm-config/blob/rawhide/f/config.guess";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/Autoconf-exception-macro.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/Autoconf-exception-macro.json";,
+      "referenceNumber": 27,
+      "name": "Autoconf macro exception",
+      "licenseExceptionId": "Autoconf-exception-macro",
+      "seeAlso": [
+        
"https://github.com/freedesktop/xorg-macros/blob/39f07f7db58ebbf3dcb64a2bf9098ed5cf3d1223/xorg-macros.m4.in";,
+        "https://www.gnu.org/software/autoconf-archive/ax_pthread.html";,
+        "https://launchpad.net/ubuntu/precise/+source/xmltooling/+copyright";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/Bison-exception-1.24.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/Bison-exception-1.24.json";,
+      "referenceNumber": 15,
+      "name": "Bison exception 1.24",
+      "licenseExceptionId": "Bison-exception-1.24",
+      "seeAlso": [
+        
"https://github.com/arineng/rwhoisd/blob/master/rwhoisd/mkdb/y.tab.c#L180";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/Bison-exception-2.2.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/Bison-exception-2.2.json";,
+      "referenceNumber": 74,
+      "name": "Bison exception 2.2",
+      "licenseExceptionId": "Bison-exception-2.2",
+      "seeAlso": [
+        
"http://git.savannah.gnu.org/cgit/bison.git/tree/data/yacc.c?id\u003d193d7c7054ba7197b0789e14965b739162319b5e#n141";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/Bootloader-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/Bootloader-exception.json";,
+      "referenceNumber": 29,
+      "name": "Bootloader Distribution Exception",
+      "licenseExceptionId": "Bootloader-exception",
+      "seeAlso": [
+        "https://github.com/pyinstaller/pyinstaller/blob/develop/COPYING.txt";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/CGAL-linking-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/CGAL-linking-exception.json";,
+      "referenceNumber": 62,
+      "name": "CGAL Linking Exception",
+      "licenseExceptionId": "CGAL-linking-exception",
+      "seeAlso": [
+        
"https://github.com/openscad/openscad/blob/openscad-2021.01/COPYING#L3";,
+        
"https://github.com/floriankirsch/OpenCSG/blob/opencsg-1-4-2-release/license.txt#L3";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/Classpath-exception-2.0.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/Classpath-exception-2.0.json";,
+      "referenceNumber": 2,
+      "name": "Classpath exception 2.0",
+      "licenseExceptionId": "Classpath-exception-2.0",
+      "seeAlso": [
+        "http://www.gnu.org/software/classpath/license.html";,
+        "https://fedoraproject.org/wiki/Licensing/GPL_Classpath_Exception";
+      ]
+    },
+    {
+      "reference": 
"https://spdx.org/licenses/Classpath-exception-2.0-short.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": 
"https://spdx.org/licenses/Classpath-exception-2.0-short.json";,
+      "referenceNumber": 52,
+      "name": "Classpath exception 2.0 - short",
+      "licenseExceptionId": "Classpath-exception-2.0-short",
+      "seeAlso": [
+        
"https://sourceforge.net/projects/lazarus/files/Lazarus%20Zip%20_%20GZip/Lazarus%204.2/lazarus-4.2-0.tar.gz/download";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/CLISP-exception-2.0.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/CLISP-exception-2.0.json";,
+      "referenceNumber": 21,
+      "name": "CLISP exception 2.0",
+      "licenseExceptionId": "CLISP-exception-2.0",
+      "seeAlso": [
+        "http://sourceforge.net/p/clisp/clisp/ci/default/tree/COPYRIGHT";
+      ]
+    },
+    {
+      "reference": 
"https://spdx.org/licenses/cryptsetup-OpenSSL-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": 
"https://spdx.org/licenses/cryptsetup-OpenSSL-exception.json";,
+      "referenceNumber": 1,
+      "name": "cryptsetup OpenSSL exception",
+      "licenseExceptionId": "cryptsetup-OpenSSL-exception",
+      "seeAlso": [
+        "https://gitlab.com/cryptsetup/cryptsetup/-/blob/main/COPYING";,
+        "https://gitlab.nic.cz/datovka/datovka/-/blob/develop/COPYING";,
+        
"https://github.com/nbs-system/naxsi/blob/951123ad456bdf5ac94e8d8819342fe3d49bc002/naxsi_src/naxsi_raw.c";,
+        "http://web.mit.edu/jgross/arch/amd64_deb60/bin/mosh";,
+        
"https://sourceforge.net/p/linux-ima/ima-evm-utils/ci/master/tree/src/evmctl.c#l30";,
+        "https://github.com/ocaml-omake/omake/blob/master/LICENSE.OMake#L20";
+      ]
+    },
+    {
+      "reference": 
"https://spdx.org/licenses/Digia-Qt-LGPL-exception-1.1.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": 
"https://spdx.org/licenses/Digia-Qt-LGPL-exception-1.1.json";,
+      "referenceNumber": 5,
+      "name": "Digia Qt LGPL Exception version 1.1",
+      "licenseExceptionId": "Digia-Qt-LGPL-exception-1.1",
+      "seeAlso": [
+        
"https://src.fedoraproject.org/rpms/qtlockedfile/blob/rawhide/f/LGPL_EXCEPTION";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/DigiRule-FOSS-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/DigiRule-FOSS-exception.json";,
+      "referenceNumber": 80,
+      "name": "DigiRule FOSS License Exception",
+      "licenseExceptionId": "DigiRule-FOSS-exception",
+      "seeAlso": [
+        "http://www.digirulesolutions.com/drupal/foss";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/eCos-exception-2.0.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/eCos-exception-2.0.json";,
+      "referenceNumber": 36,
+      "name": "eCos exception 2.0",
+      "licenseExceptionId": "eCos-exception-2.0",
+      "seeAlso": [
+        "http://ecos.sourceware.org/license-overview.html";
+      ]
+    },
+    {
+      "reference": 
"https://spdx.org/licenses/erlang-otp-linking-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": 
"https://spdx.org/licenses/erlang-otp-linking-exception.json";,
+      "referenceNumber": 26,
+      "name": "Erlang/OTP Linking Exception",
+      "licenseExceptionId": "erlang-otp-linking-exception",
+      "seeAlso": [
+        "https://www.gnu.org/licenses/gpl-faq.en.html#GPLIncompatibleLibs";,
+        "https://erlang.org/pipermail/erlang-questions/2012-May/066355.html";,
+        
"https://gitea.osmocom.org/erlang/osmo_ss7/src/commit/2286c1b8738d715950026650bf53f19a69d6ed0e/src/ss7_links.erl#L20";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/Fawkes-Runtime-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/Fawkes-Runtime-exception.json";,
+      "referenceNumber": 25,
+      "name": "Fawkes Runtime Exception",
+      "licenseExceptionId": "Fawkes-Runtime-exception",
+      "seeAlso": [
+        "http://www.fawkesrobotics.org/about/license/";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/FLTK-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/FLTK-exception.json";,
+      "referenceNumber": 32,
+      "name": "FLTK exception",
+      "licenseExceptionId": "FLTK-exception",
+      "seeAlso": [
+        "http://www.fltk.org/COPYING.php";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/fmt-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/fmt-exception.json";,
+      "referenceNumber": 72,
+      "name": "fmt exception",
+      "licenseExceptionId": "fmt-exception",
+      "seeAlso": [
+        "https://github.com/fmtlib/fmt/blob/master/LICENSE";,
+        
"https://github.com/fmtlib/fmt/blob/2eb363297b24cd71a68ccfb20ff755430f17e60f/LICENSE#L22C1-L27C62";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/Font-exception-2.0.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/Font-exception-2.0.json";,
+      "referenceNumber": 39,
+      "name": "Font exception 2.0",
+      "licenseExceptionId": "Font-exception-2.0",
+      "seeAlso": [
+        "http://www.gnu.org/licenses/gpl-faq.html#FontException";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/freertos-exception-2.0.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/freertos-exception-2.0.json";,
+      "referenceNumber": 38,
+      "name": "FreeRTOS Exception 2.0",
+      "licenseExceptionId": "freertos-exception-2.0",
+      "seeAlso": [
+        
"https://web.archive.org/web/20060809182744/http://www.freertos.org/a00114.html";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/GCC-exception-2.0.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/GCC-exception-2.0.json";,
+      "referenceNumber": 78,
+      "name": "GCC Runtime Library exception 2.0",
+      "licenseExceptionId": "GCC-exception-2.0",
+      "seeAlso": [
+        
"https://gcc.gnu.org/git/?p\u003dgcc.git;a\u003dblob;f\u003dgcc/libgcc1.c;h\u003d762f5143fc6eed57b6797c82710f3538aa52b40b;hb\u003dcb143a3ce4fb417c68f5fa2691a1b1b1053dfba9#l10";,
+        
"https://sourceware.org/git/?p\u003dglibc.git;a\u003dblob;f\u003dcsu/abi-note.c;h\u003dc2ec208e94fbe91f63d3c375bd254b884695d190;hb\u003dHEAD";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/GCC-exception-2.0-note.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/GCC-exception-2.0-note.json";,
+      "referenceNumber": 53,
+      "name": "GCC    Runtime Library exception 2.0 - note variant",
+      "licenseExceptionId": "GCC-exception-2.0-note",
+      "seeAlso": [
+        
"https://sourceware.org/git/?p\u003dglibc.git;a\u003dblob;f\u003dsysdeps/x86_64/start.S";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/GCC-exception-3.1.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/GCC-exception-3.1.json";,
+      "referenceNumber": 48,
+      "name": "GCC Runtime Library exception 3.1",
+      "licenseExceptionId": "GCC-exception-3.1",
+      "seeAlso": [
+        "http://www.gnu.org/licenses/gcc-exception-3.1.html";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/Gmsh-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/Gmsh-exception.json";,
+      "referenceNumber": 22,
+      "name": "Gmsh exception",
+      "licenseExceptionId": "Gmsh-exception",
+      "seeAlso": [
+        "https://gitlab.onelab.info/gmsh/gmsh/-/raw/master/LICENSE.txt";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/GNAT-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/GNAT-exception.json";,
+      "referenceNumber": 20,
+      "name": "GNAT exception",
+      "licenseExceptionId": "GNAT-exception",
+      "seeAlso": [
+        
"https://github.com/AdaCore/florist/blob/master/libsrc/posix-configurable_file_limits.adb";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/GNOME-examples-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/GNOME-examples-exception.json";,
+      "referenceNumber": 35,
+      "name": "GNOME examples exception",
+      "licenseExceptionId": "GNOME-examples-exception",
+      "seeAlso": [
+        
"https://gitlab.gnome.org/Archive/gnome-devel-docs/-/blob/master/platform-demos/C/legal.xml?ref_type\u003dheads";,
+        "http://meldmerge.org/help/";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/GNU-compiler-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/GNU-compiler-exception.json";,
+      "referenceNumber": 19,
+      "name": "GNU Compiler Exception",
+      "licenseExceptionId": "GNU-compiler-exception",
+      "seeAlso": [
+        
"https://sourceware.org/git?p\u003dbinutils-gdb.git;a\u003dblob;f\u003dlibiberty/unlink-if-ordinary.c;h\u003de49f2f2f67bfdb10d6b2bd579b0e01cad0fd708e;hb\u003dHEAD#l19";,
+        
"https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/powerpc/lib/crtsavres.S?h\u003dv6.16-rc6#n34";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/gnu-javamail-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/gnu-javamail-exception.json";,
+      "referenceNumber": 71,
+      "name": "GNU JavaMail exception",
+      "licenseExceptionId": "gnu-javamail-exception",
+      "seeAlso": [
+        "http://www.gnu.org/software/classpathx/javamail/javamail.html";
+      ]
+    },
+    {
+      "reference": 
"https://spdx.org/licenses/GPL-3.0-389-ds-base-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": 
"https://spdx.org/licenses/GPL-3.0-389-ds-base-exception.json";,
+      "referenceNumber": 13,
+      "name": "GPL-3.0 389 DS Base Exception",
+      "licenseExceptionId": "GPL-3.0-389-ds-base-exception",
+      "seeAlso": []
+    },
+    {
+      "reference": 
"https://spdx.org/licenses/GPL-3.0-interface-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": 
"https://spdx.org/licenses/GPL-3.0-interface-exception.json";,
+      "referenceNumber": 11,
+      "name": "GPL-3.0 Interface Exception",
+      "licenseExceptionId": "GPL-3.0-interface-exception",
+      "seeAlso": [
+        
"https://www.gnu.org/licenses/gpl-faq.en.html#LinkingOverControlledInterface";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/GPL-3.0-linking-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/GPL-3.0-linking-exception.json";,
+      "referenceNumber": 59,
+      "name": "GPL-3.0 Linking Exception",
+      "licenseExceptionId": "GPL-3.0-linking-exception",
+      "seeAlso": [
+        "https://www.gnu.org/licenses/gpl-faq.en.html#GPLIncompatibleLibs";
+      ]
+    },
+    {
+      "reference": 
"https://spdx.org/licenses/GPL-3.0-linking-source-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": 
"https://spdx.org/licenses/GPL-3.0-linking-source-exception.json";,
+      "referenceNumber": 69,
+      "name": "GPL-3.0 Linking Exception (with Corresponding Source)",
+      "licenseExceptionId": "GPL-3.0-linking-source-exception",
+      "seeAlso": [
+        "https://www.gnu.org/licenses/gpl-faq.en.html#GPLIncompatibleLibs";,
+        "https://github.com/mirror/wget/blob/master/src/http.c#L20";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/GPL-CC-1.0.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/GPL-CC-1.0.json";,
+      "referenceNumber": 40,
+      "name": "GPL Cooperation Commitment 1.0",
+      "licenseExceptionId": "GPL-CC-1.0",
+      "seeAlso": [
+        "https://github.com/gplcc/gplcc/blob/master/Project/COMMITMENT";,
+        "https://gplcc.github.io/gplcc/Project/README-PROJECT.html";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/GStreamer-exception-2005.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/GStreamer-exception-2005.json";,
+      "referenceNumber": 16,
+      "name": "GStreamer Exception (2005)",
+      "licenseExceptionId": "GStreamer-exception-2005",
+      "seeAlso": [
+        
"https://gstreamer.freedesktop.org/documentation/frequently-asked-questions/licensing.html?gi-language\u003dc#licensing-of-applications-using-gstreamer";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/GStreamer-exception-2008.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/GStreamer-exception-2008.json";,
+      "referenceNumber": 41,
+      "name": "GStreamer Exception (2008)",
+      "licenseExceptionId": "GStreamer-exception-2008",
+      "seeAlso": [
+        
"https://gstreamer.freedesktop.org/documentation/frequently-asked-questions/licensing.html?gi-language\u003dc#licensing-of-applications-using-gstreamer";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/harbour-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/harbour-exception.json";,
+      "referenceNumber": 60,
+      "name": "harbour exception",
+      "licenseExceptionId": "harbour-exception",
+      "seeAlso": [
+        "https://github.com/harbour/core/blob/master/LICENSE.txt#L44-L66";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/i2p-gpl-java-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/i2p-gpl-java-exception.json";,
+      "referenceNumber": 58,
+      "name": "i2p GPL+Java Exception",
+      "licenseExceptionId": "i2p-gpl-java-exception",
+      "seeAlso": [
+        "http://geti2p.net/en/get-involved/develop/licenses#java_exception";
+      ]
+    },
+    {
+      "reference": 
"https://spdx.org/licenses/Independent-modules-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": 
"https://spdx.org/licenses/Independent-modules-exception.json";,
+      "referenceNumber": 9,
+      "name": "Independent Module Linking exception",
+      "licenseExceptionId": "Independent-modules-exception",
+      "seeAlso": [
+        
"https://gitlab.com/freepascal.org/fpc/source/-/blob/release_3_2_2/rtl/COPYING.FPC";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/KiCad-libraries-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/KiCad-libraries-exception.json";,
+      "referenceNumber": 46,
+      "name": "KiCad Libraries Exception",
+      "licenseExceptionId": "KiCad-libraries-exception",
+      "seeAlso": [
+        "https://www.kicad.org/libraries/license/";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/kvirc-openssl-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/kvirc-openssl-exception.json";,
+      "referenceNumber": 34,
+      "name": "kvirc OpenSSL Exception",
+      "licenseExceptionId": "kvirc-openssl-exception",
+      "seeAlso": [
+        
"https://github.com/kvirc/KVIrc/blob/ba18690abb4f5ce77bb10164ee0835cc150f4a2a/doc/ABOUT-LICENSE#L34";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/LGPL-3.0-linking-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": 
"https://spdx.org/licenses/LGPL-3.0-linking-exception.json";,
+      "referenceNumber": 56,
+      "name": "LGPL-3.0 Linking Exception",
+      "licenseExceptionId": "LGPL-3.0-linking-exception",
+      "seeAlso": [
+        "https://raw.githubusercontent.com/go-xmlpath/xmlpath/v2/LICENSE";,
+        "https://github.com/goamz/goamz/blob/master/LICENSE";,
+        "https://github.com/juju/errors/blob/master/LICENSE";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/libpri-OpenH323-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/libpri-OpenH323-exception.json";,
+      "referenceNumber": 81,
+      "name": "libpri OpenH323 exception",
+      "licenseExceptionId": "libpri-OpenH323-exception",
+      "seeAlso": [
+        "https://github.com/asterisk/libpri/blob/1.6.0/README#L19-L22";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/Libtool-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/Libtool-exception.json";,
+      "referenceNumber": 14,
+      "name": "Libtool Exception",
+      "licenseExceptionId": "Libtool-exception",
+      "seeAlso": [
+        "http://git.savannah.gnu.org/cgit/libtool.git/tree/m4/libtool.m4";,
+        
"https://git.savannah.gnu.org/cgit/libtool.git/tree/libltdl/lt__alloc.c#n15";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/Linux-syscall-note.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/Linux-syscall-note.json";,
+      "referenceNumber": 44,
+      "name": "Linux Syscall Note",
+      "licenseExceptionId": "Linux-syscall-note",
+      "seeAlso": [
+        
"https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/COPYING";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/LLGPL.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/LLGPL.json";,
+      "referenceNumber": 47,
+      "name": "LLGPL Preamble",
+      "licenseExceptionId": "LLGPL",
+      "seeAlso": [
+        "http://opensource.franz.com/preamble.html";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/LLVM-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/LLVM-exception.json";,
+      "referenceNumber": 82,
+      "name": "LLVM Exception",
+      "licenseExceptionId": "LLVM-exception",
+      "seeAlso": [
+        "http://llvm.org/foundation/relicensing/LICENSE.txt";,
+        
"https://web.archive.org/web/20240423023852/https://foundation.llvm.org/relicensing/LICENSE.txt";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/LZMA-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/LZMA-exception.json";,
+      "referenceNumber": 83,
+      "name": "LZMA exception",
+      "licenseExceptionId": "LZMA-exception",
+      "seeAlso": [
+        "http://nsis.sourceforge.net/Docs/AppendixI.html#I.6";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/mif-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/mif-exception.json";,
+      "referenceNumber": 43,
+      "name": "Macros and Inline Functions Exception",
+      "licenseExceptionId": "mif-exception",
+      "seeAlso": [
+        "http://www.scs.stanford.edu/histar/src/lib/cppsup/exception";,
+        "http://dev.bertos.org/doxygen/";,
+        "https://www.threadingbuildingblocks.org/licensing";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/mxml-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/mxml-exception.json";,
+      "referenceNumber": 61,
+      "name": "mxml Exception",
+      "licenseExceptionId": "mxml-exception",
+      "seeAlso": [
+        "https://github.com/michaelrsweet/mxml/blob/master/NOTICE";,
+        "https://github.com/michaelrsweet/mxml/blob/master/LICENSE";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/Nokia-Qt-exception-1.1.html";,
+      "isDeprecatedLicenseId": true,
+      "detailsUrl": "https://spdx.org/licenses/Nokia-Qt-exception-1.1.json";,
+      "referenceNumber": 7,
+      "name": "Nokia Qt LGPL exception 1.1",
+      "licenseExceptionId": "Nokia-Qt-exception-1.1",
+      "seeAlso": [
+        
"https://www.keepassx.org/dev/projects/keepassx/repository/revisions/b8dfb9cc4d5133e0f09cd7533d15a4f1c19a40f2/entry/LICENSE.NOKIA-LGPL-EXCEPTION";
+      ]
+    },
+    {
+      "reference": 
"https://spdx.org/licenses/OCaml-LGPL-linking-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": 
"https://spdx.org/licenses/OCaml-LGPL-linking-exception.json";,
+      "referenceNumber": 54,
+      "name": "OCaml LGPL Linking Exception",
+      "licenseExceptionId": "OCaml-LGPL-linking-exception",
+      "seeAlso": [
+        "https://caml.inria.fr/ocaml/license.en.html";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/OCCT-exception-1.0.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/OCCT-exception-1.0.json";,
+      "referenceNumber": 67,
+      "name": "Open CASCADE Exception 1.0",
+      "licenseExceptionId": "OCCT-exception-1.0",
+      "seeAlso": [
+        "http://www.opencascade.com/content/licensing";
+      ]
+    },
+    {
+      "reference": 
"https://spdx.org/licenses/OpenJDK-assembly-exception-1.0.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": 
"https://spdx.org/licenses/OpenJDK-assembly-exception-1.0.json";,
+      "referenceNumber": 23,
+      "name": "OpenJDK Assembly exception 1.0",
+      "licenseExceptionId": "OpenJDK-assembly-exception-1.0",
+      "seeAlso": [
+        "http://openjdk.java.net/legal/assembly-exception.html";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/openvpn-openssl-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/openvpn-openssl-exception.json";,
+      "referenceNumber": 18,
+      "name": "OpenVPN OpenSSL Exception",
+      "licenseExceptionId": "openvpn-openssl-exception",
+      "seeAlso": [
+        "http://openvpn.net/index.php/license.html";,
+        "https://github.com/psycopg/psycopg2/blob/2_9_3/LICENSE#L14";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/PCRE2-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/PCRE2-exception.json";,
+      "referenceNumber": 55,
+      "name": "PCRE2 exception",
+      "licenseExceptionId": "PCRE2-exception",
+      "seeAlso": [
+        "https://www.pcre.org/licence.txt";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/polyparse-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/polyparse-exception.json";,
+      "referenceNumber": 6,
+      "name": "Polyparse Exception",
+      "licenseExceptionId": "polyparse-exception",
+      "seeAlso": [
+        "https://hackage.haskell.org/package/polyparse-1.13/src/COPYRIGHT";
+      ]
+    },
+    {
+      "reference": 
"https://spdx.org/licenses/PS-or-PDF-font-exception-20170817.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": 
"https://spdx.org/licenses/PS-or-PDF-font-exception-20170817.json";,
+      "referenceNumber": 66,
+      "name": "PS/PDF font exception (2017-08-17)",
+      "licenseExceptionId": "PS-or-PDF-font-exception-20170817",
+      "seeAlso": [
+        
"https://github.com/ArtifexSoftware/urw-base35-fonts/blob/65962e27febc3883a17e651cdb23e783668c996f/LICENSE";
+      ]
+    },
+    {
+      "reference": 
"https://spdx.org/licenses/QPL-1.0-INRIA-2004-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": 
"https://spdx.org/licenses/QPL-1.0-INRIA-2004-exception.json";,
+      "referenceNumber": 10,
+      "name": "INRIA QPL 1.0 2004 variant exception",
+      "licenseExceptionId": "QPL-1.0-INRIA-2004-exception",
+      "seeAlso": [
+        
"https://git.frama-c.com/pub/frama-c/-/blob/master/licenses/Q_MODIFIED_LICENSE";,
+        "https://github.com/maranget/hevea/blob/master/LICENSE";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/Qt-GPL-exception-1.0.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/Qt-GPL-exception-1.0.json";,
+      "referenceNumber": 64,
+      "name": "Qt GPL exception 1.0",
+      "licenseExceptionId": "Qt-GPL-exception-1.0",
+      "seeAlso": [
+        "http://code.qt.io/cgit/qt/qtbase.git/tree/LICENSE.GPL3-EXCEPT";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/Qt-LGPL-exception-1.1.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/Qt-LGPL-exception-1.1.json";,
+      "referenceNumber": 79,
+      "name": "Qt LGPL exception 1.1",
+      "licenseExceptionId": "Qt-LGPL-exception-1.1",
+      "seeAlso": [
+        "http://code.qt.io/cgit/qt/qtbase.git/tree/LGPL_EXCEPTION.txt";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/Qwt-exception-1.0.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/Qwt-exception-1.0.json";,
+      "referenceNumber": 75,
+      "name": "Qwt exception 1.0",
+      "licenseExceptionId": "Qwt-exception-1.0",
+      "seeAlso": [
+        "http://qwt.sourceforge.net/qwtlicense.html";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/romic-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/romic-exception.json";,
+      "referenceNumber": 76,
+      "name": "Romic Exception",
+      "licenseExceptionId": "romic-exception",
+      "seeAlso": [
+        
"https://web.archive.org/web/20210124015834/http://mo.morsi.org/blog/2009/08/13/lesser_affero_gplv3/";,
+        
"https://sourceforge.net/p/romic/code/ci/3ab2856180cf0d8b007609af53154cf092efc58f/tree/COPYING";,
+        
"https://github.com/moll/node-mitm/blob/bbf24b8bd7596dc6e091e625363161ce91984fc7/LICENSE#L8-L11";,
+        
"https://github.com/zenbones/SmallMind/blob/3c62b5995fe7f27c453f140ff9b60560a0893f2a/COPYRIGHT#L25-L30";,
+        
"https://github.com/CubeArtisan/cubeartisan/blob/2c6ab53455237b88a3ea07be02a838a135c4ab79/LICENSE.LESSER#L10-L15";,
+        
"https://github.com/savearray2/py.js/blob/b781273c08c8afa89f4954de4ecf42ec01429bae/README.md#license";
+      ]
+    },
+    {
+      "reference": 
"https://spdx.org/licenses/RRDtool-FLOSS-exception-2.0.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": 
"https://spdx.org/licenses/RRDtool-FLOSS-exception-2.0.json";,
+      "referenceNumber": 70,
+      "name": "RRDtool FLOSS exception 2.0",
+      "licenseExceptionId": "RRDtool-FLOSS-exception-2.0",
+      "seeAlso": [
+        "https://github.com/oetiker/rrdtool-1.x/blob/master/COPYRIGHT#L25-L90";,
+        "https://oss.oetiker.ch/rrdtool/license.en.html";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/rsync-linking-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/rsync-linking-exception.json";,
+      "referenceNumber": 33,
+      "name": "rsync Linking Exception",
+      "licenseExceptionId": "rsync-linking-exception",
+      "seeAlso": [
+        "https://github.com/RsyncProject/rsync/blob/master/COPYING";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/SANE-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/SANE-exception.json";,
+      "referenceNumber": 63,
+      "name": "SANE Exception",
+      "licenseExceptionId": "SANE-exception",
+      "seeAlso": [
+        "https://github.com/alexpevzner/sane-airscan/blob/master/LICENSE";,
+        
"https://gitlab.com/sane-project/backends/-/blob/master/sanei/sanei_pp.c?ref_type\u003dheads";,
+        
"https://gitlab.com/sane-project/frontends/-/blob/master/sanei/sanei_codec_ascii.c?ref_type\u003dheads";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/SHL-2.0.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/SHL-2.0.json";,
+      "referenceNumber": 24,
+      "name": "Solderpad Hardware License v2.0",
+      "licenseExceptionId": "SHL-2.0",
+      "seeAlso": [
+        "https://solderpad.org/licenses/SHL-2.0/";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/SHL-2.1.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/SHL-2.1.json";,
+      "referenceNumber": 45,
+      "name": "Solderpad Hardware License v2.1",
+      "licenseExceptionId": "SHL-2.1",
+      "seeAlso": [
+        "https://solderpad.org/licenses/SHL-2.1/";
+      ]
+    },
+    {
+      "reference": 
"https://spdx.org/licenses/Simple-Library-Usage-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": 
"https://spdx.org/licenses/Simple-Library-Usage-exception.json";,
+      "referenceNumber": 8,
+      "name": "Simple Library Usage Exception",
+      "licenseExceptionId": "Simple-Library-Usage-exception",
+      "seeAlso": [
+        "https://sourceforge.net/p/teem/code/HEAD/tree/teem/trunk/LICENSE.txt";
+      ]
+    },
+    {
+      "reference": 
"https://spdx.org/licenses/sqlitestudio-OpenSSL-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": 
"https://spdx.org/licenses/sqlitestudio-OpenSSL-exception.json";,
+      "referenceNumber": 12,
+      "name": "sqlitestudio OpenSSL exception",
+      "licenseExceptionId": "sqlitestudio-OpenSSL-exception",
+      "seeAlso": [
+        "https://github.com/pawelsalawa/sqlitestudio/blob/master/LICENSE";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/stunnel-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/stunnel-exception.json";,
+      "referenceNumber": 68,
+      "name": "stunnel Exception",
+      "licenseExceptionId": "stunnel-exception",
+      "seeAlso": [
+        "https://github.com/mtrojnar/stunnel/blob/master/COPYING.md";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/SWI-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/SWI-exception.json";,
+      "referenceNumber": 77,
+      "name": "SWI exception",
+      "licenseExceptionId": "SWI-exception",
+      "seeAlso": [
+        
"https://github.com/SWI-Prolog/packages-clpqr/blob/bfa80b9270274f0800120d5b8e6fef42ac2dc6a5/clpqr/class.pl";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/Swift-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/Swift-exception.json";,
+      "referenceNumber": 57,
+      "name": "Swift Exception",
+      "licenseExceptionId": "Swift-exception",
+      "seeAlso": [
+        "https://swift.org/LICENSE.txt";,
+        
"https://github.com/apple/swift-package-manager/blob/7ab2275f447a5eb37497ed63a9340f8a6d1e488b/LICENSE.txt#L205";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/Texinfo-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/Texinfo-exception.json";,
+      "referenceNumber": 73,
+      "name": "Texinfo exception",
+      "licenseExceptionId": "Texinfo-exception",
+      "seeAlso": [
+        
"https://git.savannah.gnu.org/cgit/automake.git/tree/lib/texinfo.tex?h\u003dv1.16.5#n23";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/u-boot-exception-2.0.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/u-boot-exception-2.0.json";,
+      "referenceNumber": 84,
+      "name": "U-Boot exception 2.0",
+      "licenseExceptionId": "u-boot-exception-2.0",
+      "seeAlso": [
+        
"http://git.denx.de/?p\u003du-boot.git;a\u003dblob;f\u003dLicenses/Exceptions";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/UBDL-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/UBDL-exception.json";,
+      "referenceNumber": 31,
+      "name": "Unmodified Binary Distribution exception",
+      "licenseExceptionId": "UBDL-exception",
+      "seeAlso": [
+        "https://github.com/ipxe/ipxe/blob/master/COPYING.UBDL";
+      ]
+    },
+    {
+      "reference": 
"https://spdx.org/licenses/Universal-FOSS-exception-1.0.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": 
"https://spdx.org/licenses/Universal-FOSS-exception-1.0.json";,
+      "referenceNumber": 50,
+      "name": "Universal FOSS Exception, Version 1.0",
+      "licenseExceptionId": "Universal-FOSS-exception-1.0",
+      "seeAlso": [
+        "https://oss.oracle.com/licenses/universal-foss-exception/";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/vsftpd-openssl-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/vsftpd-openssl-exception.json";,
+      "referenceNumber": 65,
+      "name": "vsftpd OpenSSL exception",
+      "licenseExceptionId": "vsftpd-openssl-exception",
+      "seeAlso": [
+        
"https://git.stg.centos.org/source-git/vsftpd/blob/f727873674d9c9cd7afcae6677aa782eb54c8362/f/LICENSE";,
+        "https://launchpad.net/debian/squeeze/+source/vsftpd/+copyright";,
+        "https://github.com/richardcochran/vsftpd/blob/master/COPYING";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/WxWindows-exception-3.1.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/WxWindows-exception-3.1.json";,
+      "referenceNumber": 17,
+      "name": "WxWindows Library Exception 3.1",
+      "licenseExceptionId": "WxWindows-exception-3.1",
+      "seeAlso": [
+        "http://www.opensource.org/licenses/WXwindows";
+      ]
+    },
+    {
+      "reference": "https://spdx.org/licenses/x11vnc-openssl-exception.html";,
+      "isDeprecatedLicenseId": false,
+      "detailsUrl": "https://spdx.org/licenses/x11vnc-openssl-exception.json";,
+      "referenceNumber": 42,
+      "name": "x11vnc OpenSSL Exception",
+      "licenseExceptionId": "x11vnc-openssl-exception",
+      "seeAlso": [
+        "https://github.com/LibVNC/x11vnc/blob/master/src/8to24.c#L22";
+      ]
+    }
+  ],
+  "releaseDate": "2026-02-20T00:00:00Z"
+}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib4sbom-0.10.3/lib4sbom/spdx/spdx3_generator.py 
new/lib4sbom-0.10.4/lib4sbom/spdx/spdx3_generator.py
--- old/lib4sbom-0.10.3/lib4sbom/spdx/spdx3_generator.py        2026-03-31 
13:07:41.000000000 +0200
+++ new/lib4sbom-0.10.4/lib4sbom/spdx/spdx3_generator.py        2026-04-17 
14:03:35.000000000 +0200
@@ -138,7 +138,13 @@
             "name": project_name,
             "dataLicense": data_licence_id,
             "rootElement": [bom_id],
-            "profileConformance": ["core", "software", "security", 
"simpleLicensing"],
+            "profileConformance": [
+                "core",
+                "software",
+                "security",
+                "simpleLicensing",
+                "expandedLicensing",
+            ],
         }
         self.create_type("SpdxDocument", document_properties)
 
@@ -200,7 +206,14 @@
             # Extract details of the supplier. Assume format is name (email 
address)
             pattern = r"^(.*?)(?:\s*\((.*?)\))?\s*$"
             if len(supplier) > 1:
-                match = re.search(pattern, supplier[1])
+                supplier_type = supplier[0].capitalize()
+                # Capture all data after supplier type
+                supplier_name = (
+                    component_details["supplier"][len(supplier_type) + 1 :]
+                    .strip()
+                    .rstrip("\n")
+                )
+                match = re.search(pattern, supplier_name)
                 if match:
                     name = match.group(1).strip()
                     email = match.group(2)
@@ -212,9 +225,7 @@
                             "externalIdentifierType": "email",
                         }
                         supplier_info["externalIdentifier"] = [ext_id]
-                    supplier_id = self.create_type(
-                        supplier[0].capitalize(), supplier_info
-                    )
+                    supplier_id = self.create_type(supplier_type, 
supplier_info)
             else:
                 # NOASSERTION
                 supplier_id = self.create_type("Agent", {"name": supplier[0]})
@@ -258,19 +269,62 @@
                 license_details = {}
                 license_details["relationshipType"] = license_attributes[key]
                 # Detect if a valid SPDX license. Licence expressions are 
ignored
-                licence_url = True
+                licence_url = None
                 if not self.license.license_expression(component_details[key]):
-                    licence_id = 
self.license.find_license_id(component_details[key])
-                    licence_url = self.license.get_license_url(licence_id)
-                if licence_url is not None:
-                    # create a license object and reference it
-                    licence_ref = self.create_type(
-                        "simplelicensing_LicenseExpression",
-                        {
+                    if self.license.license_exception(component_details[key]):
+                        licence_type = "expandedlicensing_ListedLicense"
+                        licence_type_data = {
+                            "simplelicensing_licenseText": 
self.license.get_license_from_exception(
+                                component_details[key]
+                            ),
+                        }
+                        licence_url_ref = self.create_type(
+                            licence_type, licence_type_data
+                        )
+                        exception_id = self.license.get_exception(
+                            component_details[key]
+                        )
+                        licence_type = 
"expandedlicensing_ListedLicenseException"
+                        licence_type_data = {
+                            "expandedlicensing_additionText": exception_id
+                        }
+                        licence_exception_url_ref = self.create_type(
+                            licence_type, licence_type_data
+                        )
+                        licence_type = "expandedlicensing_WithAdditionOperator"
+                        licence_type_data = {
+                            "expandedlicensing_subjectExtendableLicense": 
licence_url_ref,
+                            "expandedlicensing_subjectAddition": 
licence_exception_url_ref,
+                        }
+                        licence_url = True
+                    elif self.license.orlater(component_details[key]):
+                        licence_type = "expandedlicensing_ListedLicense"
+                        # Need to remove the '+' from the license identifier
+                        licence_type_data = {
+                            "simplelicensing_licenseText": 
self.license.find_license_id(
+                                component_details[key]
+                            )[:-1],
+                        }
+                        licence_url_ref = self.create_type(
+                            licence_type, licence_type_data
+                        )
+                        licence_type = "expandedlicensing_OrLaterOperator"
+                        licence_type_data = {
+                            "expandedlicensing_subjectLicense": 
licence_url_ref,
+                        }
+                        licence_url = True
+                    else:
+                        licence_id = self.license.find_license_id(
+                            component_details[key]
+                        )
+                        licence_url = self.license.get_license_url(licence_id)
+                        licence_type = "simplelicensing_LicenseExpression"
+                        licence_type_data = {
                             "simplelicensing_licenseExpression": 
component_details[key],
                             "simplelicensing_licenseListVersion": 
self.license_list_id,
-                        },
-                    )
+                        }
+                if licence_url is not None:
+                    licence_ref = self.create_type(licence_type, 
licence_type_data)
                     license_details["to"] = [licence_ref]
                 else:
                     license_details["to"] = [
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib4sbom-0.10.3/lib4sbom/spdx/spdx_generator.py 
new/lib4sbom-0.10.4/lib4sbom/spdx/spdx_generator.py
--- old/lib4sbom-0.10.3/lib4sbom/spdx/spdx_generator.py 2026-03-31 
13:07:41.000000000 +0200
+++ new/lib4sbom-0.10.4/lib4sbom/spdx/spdx_generator.py 2026-04-17 
14:03:35.000000000 +0200
@@ -453,8 +453,9 @@
         elif self.debug:
             print(f"[WARNING] **** version missing for {package}")
         if "type" in package_info:
+            # Handle SPDX mismatch of - and _ in OPERATING-SYSTEM
             component["primaryPackagePurpose"] = (
-                package_info["type"].upper().replace("-", "_")
+                package_info["type"].upper().replace("_", "-")
             )
         else:
             component["primaryPackagePurpose"] = "LIBRARY"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib4sbom-0.10.3/lib4sbom/spdx/spdx_parser.py 
new/lib4sbom-0.10.4/lib4sbom/spdx/spdx_parser.py
--- old/lib4sbom-0.10.3/lib4sbom/spdx/spdx_parser.py    2026-03-31 
13:07:41.000000000 +0200
+++ new/lib4sbom-0.10.4/lib4sbom/spdx/spdx_parser.py    2026-04-17 
14:03:35.000000000 +0200
@@ -796,7 +796,22 @@
                     licence_list_version = element.get(
                         "simplelicensing_licenseListVersion"
                     )
-
+            elif element_type == "expandedlicensing_ListedLicense":
+                licence_expression[element_id] = element.get(
+                    "simplelicensing_licenseText"
+                )
+            elif element_type == "expandedlicensing_ListedLicenseException":
+                licence_expression[element_id] = element.get(
+                    "expandedlicensing_additionText"
+                )
+            elif element_type == "expandedlicensing_WithAdditionOperator":
+                licence_expression[element_id] = (
+                    
f'{licence_expression[element.get("expandedlicensing_subjectExtendableLicense")]}
 WITH {licence_expression[element.get("expandedlicensing_subjectAddition")]}'
+                )
+            elif element_type == "expandedlicensing_OrLaterOperator":
+                licence_expression[element_id] = (
+                    
f'{licence_expression[element.get("expandedlicensing_subjectLicense")]}+'
+                )
         # Look for metadata
         # Should alays have softwareSbom element, but just in case...
         if doc_id is not None:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib4sbom-0.10.3/lib4sbom/version.py 
new/lib4sbom-0.10.4/lib4sbom/version.py
--- old/lib4sbom-0.10.3/lib4sbom/version.py     2026-03-31 13:07:41.000000000 
+0200
+++ new/lib4sbom-0.10.4/lib4sbom/version.py     2026-04-17 14:03:35.000000000 
+0200
@@ -1,4 +1,4 @@
 # Copyright (C) 2026 Anthony Harrison
 # SPDX-License-Identifier: Apache-2.0
 
-VERSION: str = "0.10.3"
+VERSION: str = "0.10.4"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib4sbom-0.10.3/test/test_license.py 
new/lib4sbom-0.10.4/test/test_license.py
--- old/lib4sbom-0.10.3/test/test_license.py    1970-01-01 01:00:00.000000000 
+0100
+++ new/lib4sbom-0.10.4/test/test_license.py    2026-04-17 14:03:35.000000000 
+0200
@@ -0,0 +1,248 @@
+import pytest
+
+from lib4sbom.license import LicenseScanner as test_module
+
+
+class TestLicenseScanner:
+
+    # Initialisation checks
+    def test_initialise(self):
+        tm = test_module()
+        assert len(tm.get_license_list()) > 0
+
+    def test_getlicense_version(self):
+        tm = test_module()
+        assert len(tm.get_license_version()) > 0
+
+    # Synonymn checks
+    def test_check_synonym(self):
+        tm = test_module()
+        assert tm.check_synonym("GPL2+") != None
+
+    def test_check_missing_synonym(self):
+        tm = test_module()
+        assert tm.check_synonym("NotaLicence") == None
+
+    # Find license checks
+
+    @pytest.mark.parametrize(
+        "license, expected_result",
+        (
+            ("", "NOASSERTION"),
+            (None, "NOASSERTION"),
+            ("NOASERTION", "NOASSERTION"),
+            ("UNKNOWN", "NOASSERTION"),
+            ("NONE", "NONE"),
+            ("MIT/Apache-2.0", "MIT OR Apache-2.0"),
+            ("Apache-2.0/MIT", "Apache-2.0 OR MIT"),
+            ("Unlicense/MIT", "Unlicense OR MIT"),
+            ("MIT/Unlicense", "MIT OR Unlicense"),
+            ("MIT or Apache-2.0", "MIT OR Apache-2.0"),
+            ("MIT And Apache-2.0", "MIT AND Apache-2.0"),
+            ("NotALicense", "NOASSERTION"),
+            ("Adaptive Public License 1.0", "APL-1.0"),
+            ("Apache-2.0 with LLVM-exception", "Apache-2.0 WITH 
LLVM-exception"),
+            ("Apache 2.0 with LLVM-exception", "Apache-2.0 WITH 
LLVM-exception"),
+            ("Apache 2.0 with LLVM Exception", "Apache-2.0 WITH 
LLVM-exception"),
+            ("NotALicence with My Exception", "NOASSERTION"),
+            ("Apache-2.0 WITH My Exception", "NOASSERTION"),
+            ("LicenseRef-MyLic", "LicenseRef-MyLic"),
+            ("GPL2+", "GPL-2.0-or-later"),
+            ("wxWindows","wxWindows"),  # Deprecated license
+            ("Apache-1.0+", "Apache-1.0+"),
+            ("NotALicence+", "NOASSERTION"),
+            ("Apache-1.0+ WITH LLVM-exception", "Apache-1.0+ WITH 
LLVM-exception"),
+        ),
+    )
+    def test_license(self, license, expected_result):
+        tm = test_module()
+        result = tm.find_license(license)
+        assert result == expected_result
+
+    @pytest.mark.parametrize(
+        "expression, expected_result",
+        (
+            ("", False),
+            ("MIT or Apache-2.0", True),
+            ("MIT And Apache-2.0", True),
+            ("MIT AND Apache-2.0", True),
+            ("MIT", False),
+            ("Apache-2.0 with LLVM Exception", False),
+            ("MIT AND Apache-1.0+", True),
+        ),
+    )
+
+    def test_license_expresion(self, expression, expected_result):
+        tm = test_module()
+        result = tm.license_expression(expression)
+        assert result == expected_result
+
+    @pytest.mark.parametrize(
+        "exception, expected_result",
+        (
+            ("", False),
+            ("MIT And Apache-2.0 WITH LLVM-expetion", True),
+            ("MIT AND Apache-2.0", False),
+            ("MIT", False),
+            ("Apache-2.0 with LLVM-exception", True),
+            ("Apache-1.0+ WITH LLVM-exception", True),
+        ),
+    )
+
+    def test_license_exception(self, exception, expected_result):
+        tm = test_module()
+        result = tm.license_exception(exception)
+        assert result == expected_result
+
+    @pytest.mark.parametrize(
+        "license, expected_result",
+        (
+            ("MIT", False),
+            ("Apache-1.0+", True),
+        ),
+    )
+    def test_orlater(self, license, expected_result):
+        tm = test_module()
+        result = tm.orlater(license)
+        assert result == expected_result
+
+    # Get routines
+
+    def test_get_license_text(self):
+        tm = test_module()
+        result = tm.get_license_text("NotALicence")
+        assert len(result) == 0
+        result = tm.get_license_text("MIT")
+        assert len(result) > 0
+        result = tm.get_license_text("Apache-1.0+")
+        assert len(result) > 0
+
+    def test_get_license_name(self):
+        tm = test_module()
+        result = tm.get_license_name("NotALicence")
+        assert result == ""
+        result = tm.get_license_name("MIT")
+        assert result == "MIT License"
+        result = tm.get_license_name("MIT+")
+        assert result == "MIT License"
+
+    def test_get_license_url(self):
+        tm = test_module()
+        result = tm.get_license_url("NotALicence")
+        assert result is None
+        result = tm.get_license_url("UNKNOWN")
+        assert result is None
+        result = tm.get_license_url("MIT")
+        assert result.startswith("http")
+        result = tm.get_license_url("Apache-2.0 WITH LLVM-exception")
+        assert result.startswith("http")
+        result = tm.get_license_url("Apache-1.0+")
+        assert result.startswith("http")
+
+    def test_osi_approved(self):
+        tm = test_module()
+        result = tm.osi_approved("NotALicence")
+        assert result == False
+        result = tm.osi_approved("UNKNOWN")
+        assert result is False
+        result = tm.osi_approved("MIT")
+        assert result == True
+        result = tm.osi_approved("Apache-1.0")
+        assert result == False
+        result = tm.osi_approved("MIT+")
+        assert result == True
+
+    def test_get_license_from_exception(self):
+        tm = test_module()
+        result = tm.get_license_from_exception("NotALicence")
+        assert result == None
+        result = tm.get_license_from_exception("Apache-2.0")
+        assert result is None
+        result = tm.get_license_from_exception("Apache-2.0 WITH 
LLVM-exception")
+        assert result == "Apache-2.0"
+        result = tm.get_license_from_exception("Apache-1.0+")
+        assert result is None
+
+
+    def test_get_exception(self):
+        tm = test_module()
+        result = tm.get_exception("NotALicence")
+        assert result == None
+        result = tm.get_exception("Apache-2.0")
+        assert result is None
+        result = tm.get_exception("Apache-2.0 WITH LLVM-exception")
+        assert result == "LLVM-exception"
+        result = tm.get_exception("Apache-1.0+")
+        assert result is None
+
+    def test_get_exception_text(self):
+        tm = test_module()
+        result = tm.get_exception_text("Apache-2.0")
+        assert len(result) == 0
+        result = tm.get_exception_text("LLVM-exception")
+        assert len(result) > 0
+        result = tm.get_exception_text("Apache-1.0+")
+        assert len(result) == 0
+
+    def test_get_exception_url(self):
+        tm = test_module()
+        result = tm.get_exception_url("Apache-2.0")
+        assert result is None
+        result = tm.get_exception_url("Apache-1.0+")
+        assert result is None
+        result = tm.get_exception_url("LLVM-exception")
+        assert result.startswith("http")
+
+    @pytest.mark.parametrize(
+        "license, expected_result",
+        (
+            ([], "unknown"),
+            (["AGPL-3.0"], "networkcopyleft"),
+            (["MIT"],"permissive"),
+            (["NotALicence"], "unknown"),
+            (["MIT","Apache-2.0"], "permissive"),
+            (["MIT","GPL-3.0+"], "copyleft"),
+            (["Apache-2.0 WITH LLVM-exception"], "permissive"),
+            (["Apache-1.0+"], "permissive"),
+        ),
+    )
+    def test_get_license_category(self, license, expected_result):
+        tm = test_module()
+        result = tm.get_license_category(license)
+        assert result == expected_result.upper()
+
+    @pytest.mark.parametrize(
+        "spdxid, expected_result",
+        (
+            ("", False),
+            ("AGPL-3.0", True),
+            ("MIT", True),
+            ("UnKNOWN", False),
+            ("Apache 2.0", False),
+            ("Apache-2.0 WITH LLVM-exception", False),
+            ("Apache-1.0+", False),
+        ),
+    )
+    def test_valid_SPDX_id (self, spdxid, expected_result):
+        tm = test_module()
+        result = tm.valid_spdx_license(spdxid)
+        assert result == expected_result
+
+    @pytest.mark.parametrize(
+        "expression, expected_result",
+        (
+            ("", []),
+            ("MIT or Apache-2.0", ["MIT", "Apache-2.0"]),
+            ("MIT And Apache-2.0", ["MIT", "Apache-2.0"]),
+            ("MIT AND Apache-2.0", ["MIT", "Apache-2.0"]),
+            ("MIT AND Apache-1.0+", ["MIT", "Apache-1.0+"]),
+            ("MIT", ["MIT"]),
+            ("MIT AND NotALicence", ["MIT", "NOASSERTION"]),
+            ("Apache-2.0 with LLVM Exception", ["Apache-2.0 WITH 
LLVM-exception"]),
+            ("Apache-2.0 WITH LLVM-exception OR MIT", ["Apache-2.0 WITH 
LLVM-exception", "MIT"]),
+        ),
+    )
+    def test_expresiion_list(self, expression, expected_result):
+        tm = test_module()
+        result = tm.expression_license_list(expression)
+        assert result == expected_result

Reply via email to