Skip to content

Commit 0e0e9fe

Browse files
committed
Fix collection issue on RDFs
1 parent 28116f8 commit 0e0e9fe

File tree

4 files changed

+68
-34
lines changed

4 files changed

+68
-34
lines changed

meta_configurator/src/components/panels/rdf/rdfStoreManager.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ export const rdfStoreManager: RdfStore & {
8989

9090
function clearStore() {
9191
_store.value = $rdf.graph();
92+
// Materialize RDF collections as rdf:first/rdf:rest triples instead of rdflib Collection terms.
93+
if (_store.value.rdfFactory?.supports) {
94+
_store.value.rdfFactory.supports.COLLECTIONS = false;
95+
}
9296
}
9397

9498
function updateStatements() {

meta_configurator/src/components/toolbar/dialogs/turtle-import/ImportTurtleDialog.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ defineExpose({show: openDialog, close: hideDialog});
6464
class="flex flex-column gap-3 bigger-dialog-content"
6565
:style="{cursor: isLoading ? 'wait' : 'default'}">
6666
<Message severity="warn" :closable="false" icon="pi pi-exclamation-triangle">
67-
Importing a Turtle file will overwrite your existing data. The schema will remain unchanged.
67+
Importing a Turtle file will overwrite your existing data. <br />
68+
The schema will remain unchanged.
6869
</Message>
6970
<div class="flex align-items-center justify-content-center gap-2">
7071
<Button label="Select File" @click="requestUploadFile" :disabled="isLoading" />

meta_configurator/src/components/toolbar/dialogs/turtle-import/__tests__/importTurtleDialog.test.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,15 @@ describe('ImportTurtleDialog', () => {
4444
},
4545
}));
4646

47-
vi.doMock('@/components/toolbar/dialogs/turtle-import/importTurtleUtils', async () => {
48-
const actual = await vi.importActual<any>(
49-
'@/components/toolbar/dialogs/turtle-import/importTurtleUtils'
50-
);
47+
const requestUploadFileToRefMock = vi.fn((resultString: {value: string}) => {
48+
resultString.value = inputTurtle;
49+
});
50+
const turtleToJsonLDMock = vi.fn().mockResolvedValue(expectedJson);
51+
52+
vi.doMock('@/components/toolbar/dialogs/turtle-import/importTurtleUtils', () => {
5153
return {
52-
...actual,
53-
requestUploadFileToRef: (resultString: {value: string}) => {
54-
resultString.value = inputTurtle;
55-
},
54+
requestUploadFileToRef: requestUploadFileToRefMock,
55+
turtleToJsonLD: turtleToJsonLDMock,
5656
};
5757
});
5858

@@ -105,6 +105,8 @@ describe('ImportTurtleDialog', () => {
105105
await flushPromises();
106106
await waitUntil(() => setDataMock.mock.calls.length > 0);
107107

108+
expect(requestUploadFileToRefMock).toHaveBeenCalledTimes(1);
109+
expect(turtleToJsonLDMock).toHaveBeenCalledWith(inputTurtle);
108110
expect(setDataMock).toHaveBeenCalledTimes(1);
109111
expect(setDataMock.mock.calls[0]?.[0]).toEqual(expectedJson);
110112

Lines changed: 52 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import {useFileDialog} from '@vueuse/core';
22
import {readFileContentToStringRef} from '@/utility/readFileContent';
33
import {RdfMediaType} from '@/components/panels/rdf/rdfEnums';
4+
import {useSettings} from '@/settings/useSettings';
45
import type {Ref} from 'vue';
5-
import * as N3 from 'n3';
6-
import * as jsonld from 'jsonld';
6+
import * as $rdf from 'rdflib';
7+
8+
const settings = useSettings();
79

810
export function requestUploadFileToRef(resultString: Ref<string>): void {
911
const {open, onChange, reset} = useFileDialog({
@@ -24,37 +26,62 @@ export function requestUploadFileToRef(resultString: Ref<string>): void {
2426
/**
2527
* Converts Turtle format RDF data to JSON-LD.
2628
* @param turtleString - The input string in Turtle format
27-
* @returns Promise resolving to a compacted JSON-LD object
29+
* @returns Promise resolving to a JSON-LD object
2830
*/
2931
export async function turtleToJsonLD(turtleString: string): Promise<any> {
30-
const {quads, prefixes} = await parseTurtle(turtleString);
31-
const nquads = await serializeToNQuads(quads);
32-
const expanded = await jsonld.fromRDF(nquads, {format: RdfMediaType.NQuads});
33-
const context = buildContext(prefixes);
34-
return Object.keys(context).length > 0 ? jsonld.compact(expanded, context) : expanded;
32+
const store = await parseTurtle(turtleString);
33+
const serialized = serialize(store, RdfMediaType.JsonLd);
34+
const parsed = typeof serialized === 'string' ? JSON.parse(serialized) : serialized;
35+
return ensureGraphWrapper(parsed);
3536
}
3637

37-
function parseTurtle(turtleString: string): Promise<{quads: N3.Quad[]; prefixes: N3.Prefixes}> {
38+
function parseTurtle(turtleString: string): Promise<$rdf.IndexedFormula> {
3839
return new Promise((resolve, reject) => {
39-
const quads: N3.Quad[] = [];
40-
new N3.Parser().parse(turtleString, (error, quad, prefixes) => {
41-
if (error) reject(error);
42-
else if (quad) quads.push(quad);
43-
else resolve({quads, prefixes: prefixes ?? {}});
44-
});
40+
const store = $rdf.graph();
41+
42+
$rdf.parse(
43+
turtleString,
44+
store as $rdf.Formula,
45+
settings.value.rdf.baseUri,
46+
RdfMediaType.Turtle,
47+
error => {
48+
if (error) {
49+
reject(error);
50+
return;
51+
}
52+
resolve(store);
53+
}
54+
);
4555
});
4656
}
4757

48-
function serializeToNQuads(quads: N3.Quad[]): Promise<string> {
49-
return new Promise((resolve, reject) => {
50-
const writer = new N3.Writer({format: 'N-Quads'});
51-
quads.forEach(q => writer.addQuad(q));
52-
writer.end((error, result) => (error ? reject(error) : resolve(result)));
53-
});
58+
function serialize(store: $rdf.IndexedFormula, format: string): string {
59+
const serialized = $rdf.serialize(
60+
null,
61+
store as $rdf.Formula,
62+
settings.value.rdf.baseUri,
63+
format
64+
);
65+
if (!serialized) {
66+
throw new Error('Failed to serialize Turtle data to JSON-LD.');
67+
}
68+
return serialized;
5469
}
5570

56-
function buildContext(prefixes: N3.Prefixes): Record<string, N3.NamedNode<string>> {
57-
return Object.fromEntries(
58-
Object.entries(prefixes).filter(([prefix]) => Boolean(prefix))
59-
) as Record<string, N3.NamedNode<string>>;
71+
function ensureGraphWrapper(data: any): any {
72+
if (!data || typeof data !== 'object' || Array.isArray(data)) {
73+
return data;
74+
}
75+
76+
if ('@graph' in data) {
77+
return data;
78+
}
79+
80+
const {'@context': context, ...rest} = data;
81+
const hasRootData = Object.keys(rest).length > 0;
82+
83+
return {
84+
...(context !== undefined ? {'@context': context} : {}),
85+
'@graph': hasRootData ? [rest] : [],
86+
};
6087
}

0 commit comments

Comments
 (0)