Repository: RDFLib/rdflib

Issue:
json-ld parser adds trailing slash to URLs that have no path
This is best described with an example. Here is a json-ld file that contains a URL that has no path:
```json
[
  {
    "@id": "https://sbolstandard.org/examples/model1",
    "http://sbols.org/v3#source": [
      {
        "@id": "http://virtualparts.org"
      }
    ]
  }
]
```

Using rdflib 6.0.2 (and 6.0.1) when the above file is loaded, the value of property `http://sbols.org/v3#source` is `http://virtualparts.org/` instead of `http://virtualparts.org`. Note the trailing `/`. This does not happen with other formats like 'nt11', 'xml', or 'ttl'. The issue appears to be with the json-ld parser, not the json-ld serializer.

I have written a self-contained program that demonstrates this issue: 

```python
import rdflib

print(f'Using rdflib {rdflib.__version__}')
g = rdflib.Graph()
g.add((rdflib.URIRef('https://sbolstandard.org/examples/model1'),
       rdflib.URIRef('http://sbols.org/v3#source'),
       rdflib.URIRef('http://virtualparts.org')))


def round_trip(rdf_format):
    print(f'-------------------- Testing {rdf_format} --------------------')
    data = g.serialize(format=rdf_format)
    # print()
    # print(f'Data:\n{data}')
    g2 = rdflib.Graph()
    g2.parse(data=data, format=rdf_format)

    g_objects = list(g.objects())
    g2_objects = list(g2.objects())
    if g_objects == g2_objects:
        print(f'Round trip {rdf_format} looks good')
    else:
        print(f'Oh no! Things are different with {rdf_format}!')
        print(f'g has {g_objects}')
        print(f'g2 has {g2_objects}')


round_trip('xml')
round_trip('ttl')
round_trip('nt11')
round_trip('json-ld')
```

Here is the output of the the program:
```
$ python3 examples/jsonldbug.py
Using rdflib 6.0.2
-------------------- Testing xml --------------------
Round trip xml looks good
-------------------- Testing ttl --------------------
Round trip ttl looks good
-------------------- Testing nt11 --------------------
Round trip nt11 looks good
-------------------- Testing json-ld --------------------
Oh no! Things are different with json-ld!
g has [rdflib.term.URIRef('http://virtualparts.org')]
g2 has [rdflib.term.URIRef('http://virtualparts.org/')]
```


Discussion:

Patch:
diff --git a/rdflib/plugins/shared/jsonld/util.py b/rdflib/plugins/shared/jsonld/util.py
index 49fbdab4..bd4c06aa 100644
--- a/rdflib/plugins/shared/jsonld/util.py
+++ b/rdflib/plugins/shared/jsonld/util.py
@@ -59,6 +59,8 @@ def norm_url(base, url):
     >>> norm_url('http://example.org/', 'http://example.org//one')
     'http://example.org//one'
     """
+    if "://" in url:
+        return url
     parts = urlsplit(urljoin(base, url))
     path = normpath(parts[2])
     if sep != "/":


Test patch:
diff --git a/test/jsonld/test_util.py b/test/jsonld/test_util.py
new file mode 100644
index 00000000..da1523ec
--- /dev/null
+++ b/test/jsonld/test_util.py
@@ -0,0 +1,89 @@
+import unittest
+from typing import NamedTuple
+
+from rdflib.plugins.shared.jsonld.util import norm_url
+
+
+class URLTests(unittest.TestCase):
+    @unittest.expectedFailure
+    def test_norm_url_xfail(self):
+        class TestSpec(NamedTuple):
+            base: str
+            url: str
+            result: str
+
+        tests = [
+            TestSpec(
+                "git+ssh://example.com:1231/some/thing/",
+                "a",
+                "git+ssh://example.com:1231/some/thing/a",
+            ),
+        ]
+
+        for test in tests:
+            (base, url, result) = test
+            with self.subTest(base=base, url=url):
+                self.assertEqual(norm_url(base, url), result)
+
+    def test_norm_url(self):
+        class TestSpec(NamedTuple):
+            base: str
+            url: str
+            result: str
+
+        tests = [
+            TestSpec("http://example.org/", "/one", "http://example.org/one"),
+            TestSpec("http://example.org/", "/one#", "http://example.org/one#"),
+            TestSpec("http://example.org/one", "two", "http://example.org/two"),
+            TestSpec("http://example.org/one/", "two", "http://example.org/one/two"),
+            TestSpec(
+                "http://example.org/",
+                "http://example.net/one",
+                "http://example.net/one",
+            ),
+            TestSpec(
+                "",
+                "1 2 3",
+                "1 2 3",
+            ),
+            TestSpec(
+                "http://example.org/",
+                "http://example.org//one",
+                "http://example.org//one",
+            ),
+            TestSpec("", "http://example.org", "http://example.org"),
+            TestSpec("", "http://example.org/", "http://example.org/"),
+            TestSpec("", "mailto:name@example.com", "mailto:name@example.com"),
+            TestSpec(
+                "http://example.org/",
+                "mailto:name@example.com",
+                "mailto:name@example.com",
+            ),
+            TestSpec("http://example.org/a/b/c", "../../z", "http://example.org/z"),
+            TestSpec("http://example.org/a/b/c", "../", "http://example.org/a/"),
+            TestSpec(
+                "",
+                "git+ssh://example.com:1231/some/thing",
+                "git+ssh://example.com:1231/some/thing",
+            ),
+            TestSpec(
+                "git+ssh://example.com:1231/some/thing",
+                "",
+                "git+ssh://example.com:1231/some/thing",
+            ),
+            TestSpec(
+                "http://example.com/RDFLib/rdflib",
+                "http://example.org",
+                "http://example.org",
+            ),
+            TestSpec(
+                "http://example.com/RDFLib/rdflib",
+                "http://example.org/",
+                "http://example.org/",
+            ),
+        ]
+
+        for test in tests:
+            (base, url, result) = test
+            with self.subTest(base=base, url=url):
+                self.assertEqual(norm_url(base, url), result)