pull: Update to the SPDX License List 3.0
authorW. Trevor King <wking@tremily.us>
Sat, 30 Dec 2017 00:04:43 +0000 (16:04 -0800)
committerW. Trevor King <wking@tremily.us>
Sat, 30 Dec 2017 23:54:32 +0000 (15:54 -0800)
As released 2017-12-28 [1].  The main change here is that the same
license can now have multiple license-list entries, with each entry
covering a common grant.  I think conflating licenses with grants is
unfortunate, and would have preferred continuing to handle version
grants with the + license expression operator [2] and possibly a new
"-ONLY" operator [3].  But that ship seems to have sailed.

This commit converts the SPDX identifiers into lists, so we can list
as many identifiers as we want for each FSF license ID.  It also adds
property docs, so folks understand the significance of the identifier
ordering.

Even before 3.0, the SPDX used per-grant identifiers for MPL-2.0 and
MPL-2.0-no-copyleft-exception [4].  But my reading of [5] gives me the
impression that the FSF's GPL-compatibility ruling hinges on section
3.3, which is what the MPL-2.0-no-copyleft-exception is turning off.
So I've left mapping at FSF MPL-2.0 -> SPDX MPL-2.0, instead of
growing it to [MPL-2.0, MPL-2.0-no-copyleft-exception].

I cheat a bit on the fsf-list-practical link, because the FSF hasn't
put an anchor on that header.  Instead, I'm using the first entry
underneath the header.

[1]: https://github.com/spdx/license-list-XML/releases/tag/v3.0
[2]: https://spdx.org/spdx-specification-21-web-version#h.jxpfx0ykyb60
[3]: https://lists.spdx.org/pipermail/spdx-legal/2017-August/002126.html
     Subject: Re: minutes, summary, next steps
     Date: Thu, 17 Aug 2017 14:37:22 -0700
     Message-ID: <20170817213722.GK23356@valgrind.tremily.us>
[4]: https://github.com/spdx/license-list-XML/issues/441
[5]: https://www.gnu.org/licenses/license-list.html#MPL-2.0

README.md
pull.py

index 4112b3026511f072380b63d6ff8480e9cb85b246..1365f67503a56e4ac137d5c994ce40cf74f60497 100644 (file)
--- a/README.md
+++ b/README.md
@@ -34,9 +34,36 @@ You can pull an individual license from a few places:
 
     * `spdx`, using [the SPDX identifiers][spdx-list].
 
+## License properties
+
+Licenses have the following properties:
+
+* FSF ID: a short slug identifying the license.
+* `name`: a short string naming the license.
+* `uri`: the FSF's recommendend URI for the license.
+* `tags`: an array of FSF categories for the license.
+    The FSF currently defines the following categories:
+
+    * `gpl-2-compatible` and `gpl-3-compatible`: [licenses that are GPL-compatible][fsf-list-gpl-compatible].
+    * `fdl-compatible`: [licenses that are FDL-compatible][fsf-list-fdl-compatible].
+    * `libre`: licenses that are either [GPL-compatible][fsf-list-gpl-compatible], [FDL-compatible][fsf-list-fdl-compatible], [or][fsf-list-free-software] [are][fsf-list-free-documentation] [otherwise][fsf-list-practical] [free][fsf-list-font].
+    * `viewpoint`: [licenses for works stating a viewpoint][fsf-list-viewpoint].
+    * `non-free`: licenses that [are][fsf-list-non-free-software] [non-free][].
+* `identifiers`: an object with mappings to other license lists.
+    This API currently [attempts](#caveats) to maintain the following mappings:
+
+    * `spdx`: For licenses with SPDX IDs, the `spdx` value will hold an array of [SPDX identifiers][spdx-list].
+        Licenses may have multiple SPDX entries when SPDX list defines per-grant IDs that share the same license (e.g. [`GPL-3.0-only`][spdx-gpl-3.0-only] and `GPL-3.0-or-later`][spdx-gpl-3.0-or-later]).
+        The first entry in the SPDX array is the one that most closely matches the FSF license.
+        For example, the FSF's [`GNUGPLv3`][fsf-gplv3] text has:
+
+        > However, most software released under GPLv2 allows you to use the terms of later versions of the GPL as well.
+
+        and the GPLv3 text [suggests an “any later version” grant][gplv3], so `GPL-3.0-or-later` is the first SPDX identifier, `GPL-3.0-only` is the second, and the deprecated `GPL-3.0` is the third.
+
 ## Caveats
 
-There are currently two hacks in [the pulling script](pull.py):
+There are currently some hacks in [the pulling script](pull.py):
 
 * `SPLITS`, which:
 
@@ -47,7 +74,10 @@ There are currently two hacks in [the pulling script](pull.py):
     Ideally this would be based on [automated license-text comparison][automated-matching], but in order for that to work this API would have to expose the license text that the FSF considered for each ID.
     Currently, [the FSF's HTML page][fsf-list] links to license source, but not in a consistent enough way for me to extract the text.
 
-Until these hacks are addressed, license IDs and the `identifiers` field should be taken with a grain of salt.
+* `TAG_OVERRIDES`, which sets `tags` where the human-readable text on the [FSF's annotated list][fsf-list] has more detail than the easily-machine-readable content.
+    For example, the FSF currently only distinguishes between `gpl-2-compatible` and `gpl-3-compatible` in text, so licenses that are only compatible with one or the other need tag overrides.
+
+Until these hacks are addressed, license IDs and the `tags` and `identifiers` field should be taken with a grain of salt.
 
 ## Contributing
 
@@ -58,8 +88,21 @@ Until these hacks are addressed, license IDs and the `identifiers` field should
 [fsf-api]: https://lists.spdx.org/pipermail/spdx-legal/2017-October/002281.html
 [fsf-freebsd-fdl]: https://www.gnu.org/licenses/license-list.html#FreeBSDDL
 [fsf-freebsd-gpl]: https://www.gnu.org/licenses/license-list.html#FreeBSD
+[fsf-gplv3]: https://www.gnu.org/licenses/license-list.html#GNUGPLv3
 [fsf-list]: https://www.gnu.org/licenses/license-list.html
+[fsf-list-gpl-compatible]: https://www.gnu.org/licenses/license-list.html#GPLCompatibleLicenses
+[fsf-list-fdl-compatible]: https://www.gnu.org/licenses/license-list.html#FDL
+[fsf-list-free-software]: https://www.gnu.org/licenses/license-list.html#GPLIncompatibleLicenses
+[fsf-list-non-free-software]: https://www.gnu.org/licenses/license-list.html#NonFreeSoftwareLicenses
+[fsf-list-free-documentation]: https://www.gnu.org/licenses/license-list.html#FreeDocumentationLicenses
+[fsf-list-non-free-documentation]: https://www.gnu.org/licenses/license-list.html#NonFreeDocumentationLicenses
+[fsf-list-practical]: https://www.gnu.org/licenses/license-list.html#GPLOther
+[fsf-list-font]: https://www.gnu.org/licenses/license-list.html#Fonts
+[fsf-list-viewpoint]: https://www.gnu.org/licenses/license-list.html#OpinionLicenses
+[gplv3-howto]: https://www.gnu.org/licenses/gpl.html#howto
 [osi-api-non-canon-2]: https://github.com/OpenSourceOrg/licenses/issues/47
 [osi-api-noncanon-1]: https://github.com/OpenSourceOrg/licenses/tree/f7ff223f9694ca0d5114fc82e43c74b5c5087891#is-this-authoritative
 [osi-api]: https://api.opensource.org/
 [spdx-list]: https://spdx.org/licenses/
+[spdx-gpl-3.0-only]: https://spdx.org/licenses/GPL-3.0-only.html
+[spdx-gpl-3.0-or-later]: https://spdx.org/licenses/GPL-3.0-or-later.html
diff --git a/pull.py b/pull.py
index 9f40335d751f36c6fe15b607a854f7842c7e69a5..993fff7510ca8a1e2034c0bf900645d6eb4c495e 100755 (executable)
--- a/pull.py
+++ b/pull.py
@@ -92,126 +92,126 @@ TAG_OVERRIDES = {
 }
 
 IDENTIFIERS = {
-    'AGPLv1.0': {'spdx': 'AGPL-1.0'},
-    'AGPLv3.0': {'spdx': 'AGPL-3.0'},
-    'AcademicFreeLicense1.1': {'spdx': 'AFL-1.1'},
-    'AcademicFreeLicense1.2': {'spdx': 'AFL-1.2'},
-    'AcademicFreeLicense2.0': {'spdx': 'AFL-2.0'},
-    'AcademicFreeLicense2.1': {'spdx': 'AFL-2.1'},
-    'AcademicFreeLicense3.0': {'spdx': 'AFL-3.0'},
-    'Aladdin': {'spdx': 'Aladdin'},
-    'apache1.1': {'spdx': 'Apache-1.1'},
-    'apache1': {'spdx': 'Apache-1.0'},
-    'apache2': {'spdx': 'Apache-2.0'},
-    'apsl1': {'spdx': 'APSL-1.0'},
-    'apsl2': {'spdx': 'APSL-2.0'},
-    'ArtisticLicense': {'spdx': 'Artistic-1.0'},
-    'ArtisticLicense2': {'spdx': 'Artistic-2.0'},
-    'BerkeleyDB': {'spdx': 'Sleepycat'},
-    'bittorrent': {'spdx': 'BitTorrent-1.1'},
-    'boost': {'spdx': 'BSL-1.0'},
-    'ccby': {'spdx': 'CC-BY-4.0'},
-    'CC-BY-NC-1.0': {'spdx': 'CC-BY-NC-1.0'},
-    'CC-BY-NC-2.0': {'spdx': 'CC-BY-NC-2.0'},
-    'CC-BY-NC-2.5': {'spdx': 'CC-BY-NC-2.5'},
-    'CC-BY-NC-3.0': {'spdx': 'CC-BY-NC-3.0'},
-    'CC-BY-NC-4.0': {'spdx': 'CC-BY-NC-4.0'},
-    'CC-BY-ND-1.0': {'spdx': 'CC-BY-ND-1.0'},
-    'CC-BY-ND-2.0': {'spdx': 'CC-BY-ND-2.0'},
-    'CC-BY-ND-2.5': {'spdx': 'CC-BY-ND-2.5'},
-    'CC-BY-ND-3.0': {'spdx': 'CC-BY-ND-3.0'},
-    'CC-BY-ND-4.0': {'spdx': 'CC-BY-ND-4.0'},
-    'ccbysa': {'spdx': 'CC-BY-SA-4.0'},
-    'CC0': {'spdx': 'CC0-1.0'},
-    'CDDL': {'spdx': 'CDDL-1.0'},
-    'CPAL': {'spdx': 'CPAL-1.0'},
-    'CeCILL': {'spdx': 'CECILL-2.0'},
-    'CeCILL-B': {'spdx': 'CECILL-B'},
-    'CeCILL-C': {'spdx': 'CECILL-C'},
-    'ClarifiedArtistic': {'spdx': 'ClArtistic'},
-    'clearbsd': {'spdx': 'BSD-3-Clause-Clear'},
-    'CommonPublicLicense10': {'spdx': 'CPL-1.0'},
-    'cpol': {'spdx': 'CPOL-1.02'},
-    'Condor': {'spdx': 'Condor-1.1'},
-    'ECL2.0': {'spdx': 'ECL-2.0'},
-    'eCos11': {'spdx': 'RHeCos-1.1'},
-    'eCos2.0': {'spdx': 'GPL-2.0+ WITH eCos-exception-2.0'},
-    'EPL': {'spdx': 'EPL-1.0'},
-    'EPL2': {'spdx': 'EPL-2.0'}, # not in license-list-XML yet
-    'EUDataGrid': {'spdx': 'EUDatagrid'},
-    'EUPL': {'spdx': 'EUPL-1.1'},
-    'Eiffel': {'spdx': 'EFL-2.0'},
-    'Expat': {'spdx': 'MIT'},
-    'FDLv1.1': {'spdx': 'GFDL-1.1'},
-    'FDLv1.2': {'spdx': 'GFDL-1.2'},
-    'FDLv1.3': {'spdx': 'GFDL-1.3'},
-    'FreeBSD': {'spdx': 'BSD-2-Clause-FreeBSD'},
-    'freetype': {'spdx': 'FTL'},
-    'GNUAllPermissive': {'spdx': 'FSFAP'},
-    'GNUGPLv3': {'spdx': 'GPL-3.0'},
-    'gnuplot': {'spdx': 'gnuplot'},
-    'GPLv2': {'spdx': 'GPL-2.0'},
-    'HPND': {'spdx': 'HPND'},
-    'IBMPL': {'spdx': 'IPL-1.0'},
-    'iMatix': {'spdx': 'iMatix'},
-    'imlib': {'spdx': 'Imlib2'},
-    'ijg': {'spdx': 'IJG'},
-    'intel': {'spdx': 'Intel'},
-    'IPAFONT': {'spdx': 'IPA'},
-    'ISC': {'spdx': 'ISC'},
-    'JSON': {'spdx': 'JSON'},
-    'LGPLv3': {'spdx': 'LGPL-3.0'},
-    'LGPLv2.1': {'spdx': 'LGPL-2.1'},
-    'LPPL-1.2': {'spdx': 'LPPL-1.2'},
-    'LPPL-1.3a': {'spdx': 'LPPL-1.3a'},
-    'lucent102': {'spdx': 'LPL-1.02'},
-    'ModifiedBSD': {'spdx': 'BSD-3-Clause'},
-    'MPL': {'spdx': 'MPL-1.1'},
-    'MPL-2.0': {'spdx':'MPL-2.0'},
-    'ms-pl': {'spdx': 'MS-PL'},
-    'ms-rl': {'spdx': 'MS-RL'},
-    'NASA': {'spdx': 'NASA-1.3'},
-    'NCSA': {'spdx':'NCSA'},
-    'newOpenLDAP': {'spdx': 'OLDAP-2.7'},
-    'Nokia': {'spdx': 'Nokia'},
-    'NoLicense': {'spdx': 'NONE'},
-    'NOSL': {'spdx': 'NOSL'},
-    'NPL-1.0': {'spdx': 'NPL-1.0'},
-    'NPL-1.1': {'spdx': 'NPL-1.1'},
-    'ODbl': {'spdx': 'ODbL-1.0'},
-    'oldOpenLDAP': {'spdx': 'OLDAP-2.3'},
-    'OpenPublicL': {'spdx': 'OPL-1.0'},
-    'OpenSSL': {'spdx': 'OpenSSL'},
-    'OriginalBSD': {'spdx': 'BSD-4-Clause'},
-    'OSL-1.0': {'spdx': 'OSL-1.0'},
-    'OSL-1.1': {'spdx': 'OSL-1.1'},
-    'OSL-2.0': {'spdx': 'OSL-2.0'},
-    'OSL-2.1': {'spdx': 'OSL-2.1'},
-    'OSL-3.0': {'spdx': 'OSL-3.0'},
-    'PHP-3.01': {'spdx': 'PHP-3.01'},
-    'Python2.0': {'spdx': 'Python-2.0'},
-    'QPL': {'spdx': 'QPL-1.0'},
-    'RPSL': {'spdx': 'RPSL-1.0'},
-    'Ruby': {'spdx': 'Ruby'},
-    'SGIFreeB': {'spdx': 'SGI-B-2.0'},
-    'SILOFL': {'spdx': 'OFL-1.1'},
-    'SPL': {'spdx': 'SPL-1.0'},
-    'StandardMLofNJ': {'spdx': 'SMLNJ'},
-    'Unlicense': {'spdx': 'Unlicense'},
-    'UPL': {'spdx': 'UPL-1.0'},
-    'Vim': {'spdx': 'Vim'},
-    'W3C': {'spdx': 'W3C'},
-    'Watcom': {'spdx': 'Watcom-1.0'},
-    'WTFPL': {'spdx': 'WTFPL'},
-    'X11License': {'spdx': 'X11'},
-    'XFree861.1License': {'spdx': 'XFree86-1.1'},
-    'xinetd': {'spdx': 'xinetd'},
-    'Yahoo': {'spdx': 'YPL-1.1'},
-    'Zend': {'spdx': 'Zend-2.0'},
-    'Zimbra': {'spdx': 'Zimbra-1.3'},
-    'ZLib': {'spdx': 'Zlib'},
-    'Zope2.0': {'spdx': 'ZPL-2.0'},
-    'Zope2.1': {'spdx': 'ZPL-2.1'},
+    'AGPLv1.0': {'spdx': ['AGPL-1.0']},
+    'AGPLv3.0': {'spdx': ['AGPL-3.0-or-later', 'AGPL-3.0-only', 'AGPL-3.0']},
+    'AcademicFreeLicense1.1': {'spdx': ['AFL-1.1']},
+    'AcademicFreeLicense1.2': {'spdx': ['AFL-1.2']},
+    'AcademicFreeLicense2.0': {'spdx': ['AFL-2.0']},
+    'AcademicFreeLicense2.1': {'spdx': ['AFL-2.1']},
+    'AcademicFreeLicense3.0': {'spdx': ['AFL-3.0']},
+    'Aladdin': {'spdx': ['Aladdin']},
+    'apache1.1': {'spdx': ['Apache-1.1']},
+    'apache1': {'spdx': ['Apache-1.0']},
+    'apache2': {'spdx': ['Apache-2.0']},
+    'apsl1': {'spdx': ['APSL-1.0']},
+    'apsl2': {'spdx': ['APSL-2.0']},
+    'ArtisticLicense': {'spdx': ['Artistic-1.0']},
+    'ArtisticLicense2': {'spdx': ['Artistic-2.0']},
+    'BerkeleyDB': {'spdx': ['Sleepycat']},
+    'bittorrent': {'spdx': ['BitTorrent-1.1']},
+    'boost': {'spdx': ['BSL-1.0']},
+    'ccby': {'spdx': ['CC-BY-4.0']},
+    'CC-BY-NC-1.0': {'spdx': ['CC-BY-NC-1.0']},
+    'CC-BY-NC-2.0': {'spdx': ['CC-BY-NC-2.0']},
+    'CC-BY-NC-2.5': {'spdx': ['CC-BY-NC-2.5']},
+    'CC-BY-NC-3.0': {'spdx': ['CC-BY-NC-3.0']},
+    'CC-BY-NC-4.0': {'spdx': ['CC-BY-NC-4.0']},
+    'CC-BY-ND-1.0': {'spdx': ['CC-BY-ND-1.0']},
+    'CC-BY-ND-2.0': {'spdx': ['CC-BY-ND-2.0']},
+    'CC-BY-ND-2.5': {'spdx': ['CC-BY-ND-2.5']},
+    'CC-BY-ND-3.0': {'spdx': ['CC-BY-ND-3.0']},
+    'CC-BY-ND-4.0': {'spdx': ['CC-BY-ND-4.0']},
+    'ccbysa': {'spdx': ['CC-BY-SA-4.0']},
+    'CC0': {'spdx': ['CC0-1.0']},
+    'CDDL': {'spdx': ['CDDL-1.0']},
+    'CPAL': {'spdx': ['CPAL-1.0']},
+    'CeCILL': {'spdx': ['CECILL-2.0']},
+    'CeCILL-B': {'spdx': ['CECILL-B']},
+    'CeCILL-C': {'spdx': ['CECILL-C']},
+    'ClarifiedArtistic': {'spdx': ['ClArtistic']},
+    'clearbsd': {'spdx': ['BSD-3-Clause-Clear']},
+    'CommonPublicLicense10': {'spdx': ['CPL-1.0']},
+    'cpol': {'spdx': ['CPOL-1.02']},
+    'Condor': {'spdx': ['Condor-1.1']},
+    'ECL2.0': {'spdx': ['ECL-2.0']},
+    'eCos11': {'spdx': ['RHeCos-1.1']},
+    'eCos2.0': {'spdx': ['GPL-2.0+ WITH eCos-exception-2.0']},
+    'EPL': {'spdx': ['EPL-1.0']},
+    'EPL2': {'spdx': ['EPL-2.0']}, # not in license-list-XML yet
+    'EUDataGrid': {'spdx': ['EUDatagrid']},
+    'EUPL': {'spdx': ['EUPL-1.1']},
+    'Eiffel': {'spdx': ['EFL-2.0']},
+    'Expat': {'spdx': ['MIT']},
+    'FDLv1.1': {'spdx': ['GFDL-1.1-or-later', 'GFDL-1.1-only', 'GFDL-1.1']},
+    'FDLv1.2': {'spdx': ['GFDL-1.2-or-later', 'GFDL-1.2-only', 'GFDL-1.2']},
+    'FDLv1.3': {'spdx': ['GFDL-1.3-or-later', 'GFDL-1.3-only', 'GFDL-1.3']},
+    'FreeBSD': {'spdx': ['BSD-2-Clause-FreeBSD']},
+    'freetype': {'spdx': ['FTL']},
+    'GNUAllPermissive': {'spdx': ['FSFAP']},
+    'GNUGPLv3': {'spdx': ['GPL-3.0-or-later', 'GPL-3.0-only', 'GPL-3.0']},
+    'gnuplot': {'spdx': ['gnuplot']},
+    'GPLv2': {'spdx': ['GPL-2.0-or-later', 'GPL-2.0-only', 'GPL-2.0']},
+    'HPND': {'spdx': ['HPND']},
+    'IBMPL': {'spdx': ['IPL-1.0']},
+    'iMatix': {'spdx': ['iMatix']},
+    'imlib': {'spdx': ['Imlib2']},
+    'ijg': {'spdx': ['IJG']},
+    'intel': {'spdx': ['Intel']},
+    'IPAFONT': {'spdx': ['IPA']},
+    'ISC': {'spdx': ['ISC']},
+    'JSON': {'spdx': ['JSON']},
+    'LGPLv3': {'spdx': ['LGPL-3.0-or-later', 'LGPL-3.0-only', 'LGPL-3.0']},
+    'LGPLv2.1': {'spdx': ['LGPL-2.1-or-later', 'LGPL-2.1-only', 'LGPL-2.1']},
+    'LPPL-1.2': {'spdx': ['LPPL-1.2']},
+    'LPPL-1.3a': {'spdx': ['LPPL-1.3a']},
+    'lucent102': {'spdx': ['LPL-1.02']},
+    'ModifiedBSD': {'spdx': ['BSD-3-Clause']},
+    'MPL': {'spdx': ['MPL-1.1']},
+    'MPL-2.0': {'spdx': ['MPL-2.0']},
+    'ms-pl': {'spdx': ['MS-PL']},
+    'ms-rl': {'spdx': ['MS-RL']},
+    'NASA': {'spdx': ['NASA-1.3']},
+    'NCSA': {'spdx': ['NCSA']},
+    'newOpenLDAP': {'spdx': ['OLDAP-2.7']},
+    'Nokia': {'spdx': ['Nokia']},
+    'NoLicense': {'spdx': ['NONE']},
+    'NOSL': {'spdx': ['NOSL']},
+    'NPL-1.0': {'spdx': ['NPL-1.0']},
+    'NPL-1.1': {'spdx': ['NPL-1.1']},
+    'ODbl': {'spdx': ['ODbL-1.0']},
+    'oldOpenLDAP': {'spdx': ['OLDAP-2.3']},
+    'OpenPublicL': {'spdx': ['OPL-1.0']},
+    'OpenSSL': {'spdx': ['OpenSSL']},
+    'OriginalBSD': {'spdx': ['BSD-4-Clause']},
+    'OSL-1.0': {'spdx': ['OSL-1.0']},
+    'OSL-1.1': {'spdx': ['OSL-1.1']},
+    'OSL-2.0': {'spdx': ['OSL-2.0']},
+    'OSL-2.1': {'spdx': ['OSL-2.1']},
+    'OSL-3.0': {'spdx': ['OSL-3.0']},
+    'PHP-3.01': {'spdx': ['PHP-3.01']},
+    'Python2.0': {'spdx': ['Python-2.0']},
+    'QPL': {'spdx': ['QPL-1.0']},
+    'RPSL': {'spdx': ['RPSL-1.0']},
+    'Ruby': {'spdx': ['Ruby']},
+    'SGIFreeB': {'spdx': ['SGI-B-2.0']},
+    'SILOFL': {'spdx': ['OFL-1.1']},
+    'SPL': {'spdx': ['SPL-1.0']},
+    'StandardMLofNJ': {'spdx': ['SMLNJ']},
+    'Unlicense': {'spdx': ['Unlicense']},
+    'UPL': {'spdx': ['UPL-1.0']},
+    'Vim': {'spdx': ['Vim']},
+    'W3C': {'spdx': ['W3C']},
+    'Watcom': {'spdx': ['Watcom-1.0']},
+    'WTFPL': {'spdx': ['WTFPL']},
+    'X11License': {'spdx': ['X11']},
+    'XFree861.1License': {'spdx': ['XFree86-1.1']},
+    'xinetd': {'spdx': ['xinetd']},
+    'Yahoo': {'spdx': ['YPL-1.1']},
+    'Zend': {'spdx': ['Zend-2.0']},
+    'Zimbra': {'spdx': ['Zimbra-1.3']},
+    'ZLib': {'spdx': ['Zlib']},
+    'Zope2.0': {'spdx': ['ZPL-2.0']},
+    'Zope2.1': {'spdx': ['ZPL-2.1']},
 }
 
 
@@ -287,11 +287,14 @@ def save(licenses, dir=os.curdir):
         with open(license_path, 'w') as f:
             json.dump(obj=license, fp=f, indent=2, sort_keys=True)
             f.write('\n')
-        for scheme, identifier in license.get('identifiers', {}).items():
+        for scheme, identifiers in license.get('identifiers', {}).items():
             scheme_dir = os.path.join(dir, scheme)
             os.makedirs(scheme_dir, exist_ok=True)
-            id_path = os.path.join(scheme_dir, '{}.json'.format(identifier))
-            os.link(license_path, id_path)
+            if isinstance(identifiers, str):
+                identifiers = [identifiers]
+            for identifier in identifiers:
+                id_path = os.path.join(scheme_dir, '{}.json'.format(identifier))
+                os.link(license_path, id_path)
     with open(os.path.join(dir, 'licenses-full.json'), 'w') as f:
         json.dump(obj=full_index, fp=f, indent=2, sort_keys=True)
         f.write('\n')