diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a82fad2..cbfc6c7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ ### Fixed - Preserved RDF literal lexical forms when converting through RDFLib, including canonical double output, large numeric values, and compound literal handling. +- Fixed the active context clone dropping the default base direction: `@direction` now survives context layers like `@language`. Fixes [issue #337](https://github.com/digitalbazaar/pyld/issues/337). +- Fixed `KeyError` when a context sets `@direction: null` and no default base direction is in effect. - Fixed `iri_resolver.unresolve()` query/fragment reconstruction while cleaning up the resolver docstring. - Invalid IRI base values within lists are now skipped when serializing to RDF. Fixes [toRdf#tli12](https://w3c.github.io/json-ld-api/tests/toRdf-manifest#tli12) and [toRdf#tli14](https://w3c.github.io/json-ld-api/tests/toRdf-manifest#tli14). - Literals with invalid `@language` no longer output triples. Fixes [toRdf#twf05](https://w3c.github.io/json-ld-api/tests/toRdf-manifest#twf05). diff --git a/lib/pyld/jsonld.py b/lib/pyld/jsonld.py index 5a917b79..f8ffb1f7 100644 --- a/lib/pyld/jsonld.py +++ b/lib/pyld/jsonld.py @@ -3536,7 +3536,8 @@ def _process_context( code='invalid context entry', ) if value is None: - del rval['@direction'] + # mirror JS `delete`: a no-op when no direction is set + rval.pop('@direction', None) elif value != 'ltr' and value != 'rtl': raise JsonLdError( 'Invalid JSON-LD syntax; @direction value must be null, "ltr", or "rtl".', @@ -6310,6 +6311,8 @@ def _clone_active_context(self, active_ctx): child['previousContext'] = active_ctx['previousContext'] if '@language' in active_ctx: child['@language'] = active_ctx['@language'] + if '@direction' in active_ctx: + child['@direction'] = active_ctx['@direction'] if '@vocab' in active_ctx: child['@vocab'] = active_ctx['@vocab'] return child diff --git a/tests/jsonld/test_expand.py b/tests/jsonld/test_expand.py index cf75da0a..4102f830 100644 --- a/tests/jsonld/test_expand.py +++ b/tests/jsonld/test_expand.py @@ -568,3 +568,109 @@ def test_blank_node_prefixes(): expanded = jsonld.expand(input) assert expanded == expected + + +# Issue 337 +def test_default_direction_survives_context_layers(): + """ + The default @direction should be kept across context layers, like the + default @language. + """ + input = { + "@context": [ + {"@language": "en", "@direction": "rtl"}, + {"dummy": "http://example.com/dummy"}, + ], + "http://example.com/p": "v", + } + + expected = [ + { + "http://example.com/p": [ + {"@language": "en", "@direction": "rtl", "@value": "v"} + ], + } + ] + + expanded = jsonld.expand(input) + + assert expanded == expected + +# Issue 337 +def test_default_direction_inherited_into_scoped_context(): + """ + A property-scoped context should inherit the default @direction. + """ + input = { + "@context": { + "@language": "en", + "@direction": "rtl", + "thing": { + "@id": "http://example.com/thing", + "@context": {"other": "http://example.com/other"}, + }, + }, + "thing": {"http://example.com/label": "hello"}, + } + + expected = [ + { + "http://example.com/thing": [ + { + "http://example.com/label": [ + { + "@language": "en", + "@direction": "rtl", + "@value": "hello", + } + ], + } + ], + } + ] + + expanded = jsonld.expand(input) + + assert expanded == expected + +# Issue 337 +def test_scoped_context_can_override_or_clear_default_direction(): + """ + A scoped @direction entry should still override or clear the default. + """ + input = { + "@context": { + "@direction": "rtl", + "ltr": { + "@id": "http://example.com/ltr", + "@context": {"@direction": "ltr"}, + }, + "none": { + "@id": "http://example.com/none", + "@context": {"@direction": None}, + }, + }, + "ltr": {"http://example.com/label": "a"}, + "none": {"http://example.com/label": "b"}, + } + + expected = [ + { + "http://example.com/ltr": [ + { + "http://example.com/label": [ + {"@direction": "ltr", "@value": "a"} + ], + } + ], + "http://example.com/none": [ + { + "http://example.com/label": [{"@value": "b"}], + } + ], + } + ] + + expanded = jsonld.expand(input) + + assert expanded == expected