diff --git a/README.md b/README.md index 861ccae..5410f4e 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,26 @@ A parser for the [Open Publication Distribution System 2.0 (OPDS 2.0)](https://drafts.opds.io/opds-2.0), and [Open Distribution to Libraries 1.0 (ODL)](https://drafts.opds.io/odl-1.0.html) formats. +**Note**: This parser varys from the OPDS 2 + ODL spec in that it allows OPDS 2 + ODL feeds to contain +non-open access acquisition links. + +The spec [defines](https://drafts.opds.io/odl-1.0.html#21-opds-20) an OPDS 2 + ODL feed as: + +- It must be a valid OPDS Feed as defined in [[OPDS-2](https://drafts.opds.io/odl-1.0.html#normative-references)] with + one difference: + - The requirement for the presence of an Acquisition Link is relaxed + - Instead, each Publication listed in publications must either contain a licenses subcollection or an Open-Access + Acquisition Link (http://opds-spec.org/acquisition/open-access) + +The requirement that each link be an Open-Access Acquisition Link is overly restrictive, and prevents us from importing +mixed OPDS2 and OPDS2 + ODL feeds. We relax the requirement to: + +- It must be a valid OPDS Feed as defined in [[OPDS-2](https://drafts.opds.io/odl-1.0.html#normative-references)] with + one difference: + - The requirement for the presence of an Acquisition Link is relaxed + - Instead, each Publication listed in publications must either contain a licenses subcollection or an + **Acquisition Link** (http://opds-spec.org/acquisition) + ## Usage Install the library with `pip` diff --git a/src/webpub_manifest_parser/odl/semantic.py b/src/webpub_manifest_parser/odl/semantic.py index 128cf9e..324d2f3 100644 --- a/src/webpub_manifest_parser/odl/semantic.py +++ b/src/webpub_manifest_parser/odl/semantic.py @@ -108,10 +108,10 @@ def _format_message( message="ODL feed '{0}' contains redundant 'navigation' subcollection", ) -ODL_PUBLICATION_MUST_CONTAIN_EITHER_LICENSES_OR_OA_ACQUISITION_LINK_ERROR = partial( +ODL_PUBLICATION_MUST_CONTAIN_EITHER_LICENSES_OR_ACQUISITION_LINK_ERROR = partial( ODLPublicationSemanticError, message="ODL publication '{0}' contains neither 'licenses' subcollection nor " - "an Open-Access Acquisition Link (http://opds-spec.org/acquisition/open-access)", + "an Acquisition Link (http://opds-spec.org/acquisition)", ) ODL_LICENSE_MUST_CONTAIN_SELF_LINK_TO_LICENSE_INFO_DOCUMENT_ERROR = partial( @@ -195,12 +195,19 @@ def visit(self, node): """ self._logger.debug(f"Started processing {encode(node)}") + links = node.links or [] + acquisition_uri = OPDS2LinkRelationsRegistry.ACQUISITION.key + acquisition_links = [ + l + for l in links + if any([rel.startswith(acquisition_uri) for rel in l.rels or []]) + ] + if (not node.licenses or len(node.licenses) == 0) and ( - (not node.links or len(node.links) == 0) - or not node.links.get_by_rel(OPDS2LinkRelationsRegistry.OPEN_ACCESS.key) + (not node.links or len(node.links) == 0) or (len(acquisition_links) == 0) ): with self._record_errors(): - raise ODL_PUBLICATION_MUST_CONTAIN_EITHER_LICENSES_OR_OA_ACQUISITION_LINK_ERROR( + raise ODL_PUBLICATION_MUST_CONTAIN_EITHER_LICENSES_OR_ACQUISITION_LINK_ERROR( node=node, node_property=None ) elif node.licenses: diff --git a/tests/webpub_manifest_parser/odl/test_semantic.py b/tests/webpub_manifest_parser/odl/test_semantic.py index 49afe8c..7d9ef79 100644 --- a/tests/webpub_manifest_parser/odl/test_semantic.py +++ b/tests/webpub_manifest_parser/odl/test_semantic.py @@ -22,7 +22,7 @@ ODL_FEED_MISSING_PUBLICATIONS_SUBCOLLECTION_ERROR, ODL_LICENSE_MUST_CONTAIN_CHECKOUT_LINK_TO_LICENSE_STATUS_DOCUMENT_ERROR, ODL_LICENSE_MUST_CONTAIN_SELF_LINK_TO_LICENSE_INFO_DOCUMENT_ERROR, - ODL_PUBLICATION_MUST_CONTAIN_EITHER_LICENSES_OR_OA_ACQUISITION_LINK_ERROR, + ODL_PUBLICATION_MUST_CONTAIN_EITHER_LICENSES_OR_ACQUISITION_LINK_ERROR, ODLSemanticAnalyzer, ) from webpub_manifest_parser.opds2 import ( @@ -123,7 +123,7 @@ class ODLSemanticAnalyzerTest(AnalyzerTest): ], ), ( - "when_publication_does_not_contain_neither_licenses_nor_oa_link", + "when_publication_contains_neither_licenses_nor_acquisition_link", ODLFeed( metadata=OPDS2FeedMetadata(title="test"), links=LinkList( @@ -146,7 +146,7 @@ class ODLSemanticAnalyzerTest(AnalyzerTest): ), ), [ - ODL_PUBLICATION_MUST_CONTAIN_EITHER_LICENSES_OR_OA_ACQUISITION_LINK_ERROR( + ODL_PUBLICATION_MUST_CONTAIN_EITHER_LICENSES_OR_ACQUISITION_LINK_ERROR( node=ODLPublication( metadata=OPDS2PublicationMetadata(title="Publication 1") ), @@ -155,7 +155,7 @@ class ODLSemanticAnalyzerTest(AnalyzerTest): ], ), ( - "when_publication_does_not_contain_neither_licenses_nor_oa_link", + "when_publication_contains_neither_licenses_nor_acquisition_link", ODLFeed( metadata=OPDS2FeedMetadata(title="test"), links=LinkList( @@ -178,7 +178,7 @@ class ODLSemanticAnalyzerTest(AnalyzerTest): ), ), [ - ODL_PUBLICATION_MUST_CONTAIN_EITHER_LICENSES_OR_OA_ACQUISITION_LINK_ERROR( + ODL_PUBLICATION_MUST_CONTAIN_EITHER_LICENSES_OR_ACQUISITION_LINK_ERROR( node=ODLPublication( metadata=OPDS2PublicationMetadata(title="Publication 1") ), @@ -220,6 +220,74 @@ class ODLSemanticAnalyzerTest(AnalyzerTest): ), [], ), + ( + "when_publication_does_not_contain_licenses_and_has_an_acquisition_link", + ODLFeed( + metadata=OPDS2FeedMetadata(title="test"), + links=LinkList( + [ + Link( + href="http://example.com", + rels=[LinkRelationsRegistry.SELF.key], + ) + ] + ), + publications=CollectionList( + [ + ODLPublication( + metadata=OPDS2PublicationMetadata( + title="Publication 1" + ), + links=LinkList( + [ + Link( + href="http://example.com", + rels=[ + OPDS2LinkRelationsRegistry.ACQUISITION.key + ], + ) + ] + ), + ) + ] + ), + ), + [], + ), + ( + "when_publication_does_not_contain_licenses_and_has_an_unknown_acquisition_link", + ODLFeed( + metadata=OPDS2FeedMetadata(title="test"), + links=LinkList( + [ + Link( + href="http://example.com", + rels=[LinkRelationsRegistry.SELF.key], + ) + ] + ), + publications=CollectionList( + [ + ODLPublication( + metadata=OPDS2PublicationMetadata( + title="Publication 1" + ), + links=LinkList( + [ + Link( + href="http://example.com", + rels=[ + f"{OPDS2LinkRelationsRegistry.ACQUISITION.key}/unknown" + ], + ) + ] + ), + ) + ] + ), + ), + [], + ), ( "when_license_does_not_contain_self_link_and_borrow_link", ODLFeed(