Skip to content

Commit 28a6190

Browse files
authored
feat: sort longturtle blank nodes (#2997)
* feat: sort longturtle blank nodes in the object position by their cbd string * fix: #2767
1 parent 08dd4b7 commit 28a6190

File tree

3 files changed

+108
-77
lines changed

3 files changed

+108
-77
lines changed

rdflib/plugins/serializers/longturtle.py

+29
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,34 @@ def predicateList(self, subject, newline=False):
293293
def verb(self, node, newline=False):
294294
self.path(node, VERB, newline)
295295

296+
def sortObjects(
297+
self, values: list[URIRef | BNode | Literal]
298+
) -> list[URIRef | BNode | Literal]:
299+
"""
300+
Perform a sort on the values where each value is a blank node. Grab the CBD of the
301+
blank node and sort it by its longturtle serialization value.
302+
303+
Identified nodes come first and the sorted blank nodes are tacked on after.
304+
"""
305+
bnode_map: dict[BNode, list[str]] = {}
306+
objects = []
307+
for value in values:
308+
if isinstance(value, BNode):
309+
bnode_map[value] = []
310+
else:
311+
objects.append(value)
312+
313+
for bnode in bnode_map:
314+
cbd = self.store.cbd(bnode).serialize(format="longturtle")
315+
bnode_map[bnode].append(cbd)
316+
317+
sorted_bnodes = sorted(
318+
[(k, v) for k, v in bnode_map.items()], key=lambda x: x[1]
319+
)
320+
return objects + [x[0] for x in sorted_bnodes]
321+
296322
def objectList(self, objects):
323+
objects = self.sortObjects(objects)
297324
count = len(objects)
298325
if count == 0:
299326
return
@@ -303,6 +330,8 @@ def objectList(self, objects):
303330
if count > 1:
304331
if not isinstance(objects[0], BNode):
305332
self.write("\n" + self.indent(1))
333+
else:
334+
self.write(" ")
306335
first_nl = True
307336
self.path(objects[0], OBJECT, newline=first_nl)
308337
for obj in objects[1:]:
+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
PREFIX cn: <https://linked.data.gov.au/def/cn/>
2+
PREFIX ex: <http://example.com/>
3+
PREFIX geo: <http://www.opengis.net/ont/geosparql#>
4+
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
5+
PREFIX sdo: <https://schema.org/>
6+
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
7+
8+
ex:nicholas
9+
a sdo:Person ;
10+
sdo:age 41 ;
11+
sdo:alternateName
12+
"N.J. Car" ,
13+
"Nick Car" ,
14+
[
15+
sdo:name "Dr N.J. Car" ;
16+
] ;
17+
sdo:name
18+
[
19+
a cn:CompoundName ;
20+
sdo:hasPart
21+
[
22+
a cn:CompoundName ;
23+
rdf:value "John" ;
24+
] ,
25+
[
26+
a cn:CompoundName ;
27+
rdf:value "Nicholas" ;
28+
] ,
29+
[
30+
a cn:CompoundName ;
31+
sdo:hasPart
32+
[
33+
a cn:CompoundName ;
34+
rdf:value "Car" ;
35+
] ,
36+
[
37+
a cn:CompoundName ;
38+
rdf:value "Maxov" ;
39+
] ;
40+
] ;
41+
] ;
42+
sdo:worksFor <https://kurrawong.ai> ;
43+
.
44+
45+
<https://kurrawong.ai>
46+
a sdo:Organization ;
47+
sdo:location <https://kurrawong.ai/hq> ;
48+
.
49+
50+
<https://kurrawong.ai/hq>
51+
a sdo:Place ;
52+
sdo:address
53+
[
54+
a sdo:PostalAddress ;
55+
sdo:addressCountry
56+
[
57+
sdo:identifier "au" ;
58+
sdo:name "Australia" ;
59+
] ;
60+
sdo:addressLocality "Shorncliffe" ;
61+
sdo:addressRegion "QLD" ;
62+
sdo:postalCode 4017 ;
63+
sdo:streetAddress (
64+
72
65+
"Yundah"
66+
"Street"
67+
) ;
68+
] ;
69+
sdo:geo
70+
[
71+
sdo:polygon "POLYGON((153.082403 -27.325801, 153.08241 -27.32582, 153.082943 -27.325612, 153.083010 -27.325742, 153.083543 -27.325521, 153.083456 -27.325365, 153.082403 -27.325801))"^^geo:wktLiteral ;
72+
] ;
73+
sdo:name "KurrawongAI HQ" ;
74+
.

test/test_serializers/test_serializer_longturtle.py

+5-77
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import difflib
2-
from textwrap import dedent
2+
from pathlib import Path
33

44
from rdflib import Graph, Namespace
55
from rdflib.namespace import GEO, SDO
@@ -170,83 +170,11 @@ def test_longturtle():
170170
output = g.serialize(format="longturtle")
171171

172172
# fix the target
173-
target = dedent(
174-
""" PREFIX cn: <https://linked.data.gov.au/def/cn/>
175-
PREFIX ex: <http://example.com/>
176-
PREFIX geo: <http://www.opengis.net/ont/geosparql#>
177-
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
178-
PREFIX sdo: <https://schema.org/>
179-
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
173+
current_dir = Path.cwd() # Get the current directory
174+
target_file_path = current_dir / "test/data/longturtle" / "longturtle-target.ttl"
180175

181-
ex:nicholas
182-
a sdo:Person ;
183-
sdo:age 41 ;
184-
sdo:alternateName
185-
[
186-
sdo:name "Dr N.J. Car" ;
187-
] ,
188-
"N.J. Car" ,
189-
"Nick Car" ;
190-
sdo:name
191-
[
192-
a cn:CompoundName ;
193-
sdo:hasPart
194-
[
195-
a cn:CompoundName ;
196-
rdf:value "Nicholas" ;
197-
] ,
198-
[
199-
a cn:CompoundName ;
200-
rdf:value "John" ;
201-
] ,
202-
[
203-
a cn:CompoundName ;
204-
sdo:hasPart
205-
[
206-
a cn:CompoundName ;
207-
rdf:value "Car" ;
208-
] ,
209-
[
210-
a cn:CompoundName ;
211-
rdf:value "Maxov" ;
212-
] ;
213-
] ;
214-
] ;
215-
sdo:worksFor <https://kurrawong.ai> ;
216-
.
217-
218-
<https://kurrawong.ai>
219-
a sdo:Organization ;
220-
sdo:location <https://kurrawong.ai/hq> ;
221-
.
222-
223-
<https://kurrawong.ai/hq>
224-
a sdo:Place ;
225-
sdo:address
226-
[
227-
a sdo:PostalAddress ;
228-
sdo:addressCountry
229-
[
230-
sdo:identifier "au" ;
231-
sdo:name "Australia" ;
232-
] ;
233-
sdo:addressLocality "Shorncliffe" ;
234-
sdo:addressRegion "QLD" ;
235-
sdo:postalCode 4017 ;
236-
sdo:streetAddress (
237-
72
238-
"Yundah"
239-
"Street"
240-
) ;
241-
] ;
242-
sdo:geo
243-
[
244-
sdo:polygon "POLYGON((153.082403 -27.325801, 153.08241 -27.32582, 153.082943 -27.325612, 153.083010 -27.325742, 153.083543 -27.325521, 153.083456 -27.325365, 153.082403 -27.325801))"^^geo:wktLiteral ;
245-
] ;
246-
sdo:name "KurrawongAI HQ" ;
247-
.
248-
"""
249-
)
176+
with open(target_file_path, encoding="utf-8") as file:
177+
target = file.read()
250178

251179
# compare output to target
252180
# - any differences will produce output

0 commit comments

Comments
 (0)