Skip to content

Commit e864607

Browse files
Add conneg support for HTML with JSON-LD data islands
1 parent 9e381f5 commit e864607

1 file changed

Lines changed: 62 additions & 30 deletions

File tree

src/handlers/resource.js

Lines changed: 62 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -192,38 +192,70 @@ export async function handleGet(request, reply) {
192192
return reply.code(500).send({ error: 'Read error' });
193193
}
194194

195-
// Content negotiation for RDF resources
196-
if (connegEnabled && isRdfContentType(storedContentType)) {
197-
try {
198-
// Parse stored content as JSON-LD
199-
const jsonLd = JSON.parse(content.toString());
200-
201-
// Select output format based on Accept header
202-
const acceptHeader = request.headers.accept;
203-
const targetType = selectContentType(acceptHeader, connegEnabled);
204-
205-
// Convert to requested format
206-
const { content: outputContent, contentType: outputType } = await fromJsonLd(
207-
jsonLd,
208-
targetType,
209-
resourceUrl,
210-
connegEnabled
211-
);
195+
// Content negotiation for RDF resources (including HTML with JSON-LD data islands)
196+
if (connegEnabled) {
197+
const contentStr = content.toString();
198+
const acceptHeader = request.headers.accept || '';
199+
const wantsTurtle = acceptHeader.includes('text/turtle') ||
200+
acceptHeader.includes('text/n3') ||
201+
acceptHeader.includes('application/n-triples');
202+
203+
// Check if this is HTML with JSON-LD data island
204+
const isHtmlWithDataIsland = contentStr.trimStart().startsWith('<!DOCTYPE') ||
205+
contentStr.trimStart().startsWith('<html');
206+
207+
if (isHtmlWithDataIsland && wantsTurtle) {
208+
// Extract JSON-LD from HTML data island and convert to Turtle
209+
try {
210+
const jsonLdMatch = contentStr.match(/<script\s+type=["']application\/ld\+json["']\s*>([\s\S]*?)<\/script>/i);
211+
if (jsonLdMatch) {
212+
const jsonLd = JSON.parse(jsonLdMatch[1]);
213+
const { content: turtleContent } = await fromJsonLd(jsonLd, 'text/turtle', resourceUrl, true);
214+
215+
const headers = getAllHeaders({
216+
isContainer: false,
217+
etag: stats.etag,
218+
contentType: 'text/turtle',
219+
origin,
220+
resourceUrl,
221+
connegEnabled
222+
});
223+
headers['Vary'] = getVaryHeader(connegEnabled, request.mashlibEnabled);
212224

213-
const headers = getAllHeaders({
214-
isContainer: false,
215-
etag: stats.etag,
216-
contentType: outputType,
217-
origin,
218-
resourceUrl,
219-
connegEnabled
220-
});
221-
headers['Vary'] = getVaryHeader(connegEnabled, request.mashlibEnabled);
225+
Object.entries(headers).forEach(([k, v]) => reply.header(k, v));
226+
return reply.send(turtleContent);
227+
}
228+
} catch (err) {
229+
// Fall through to serve HTML if conversion fails
230+
console.error('Failed to convert HTML data island to Turtle:', err.message);
231+
}
232+
} else if (isRdfContentType(storedContentType)) {
233+
// Plain JSON-LD file
234+
try {
235+
const jsonLd = JSON.parse(contentStr);
236+
const targetType = selectContentType(acceptHeader, connegEnabled);
237+
const { content: outputContent, contentType: outputType } = await fromJsonLd(
238+
jsonLd,
239+
targetType,
240+
resourceUrl,
241+
connegEnabled
242+
);
243+
244+
const headers = getAllHeaders({
245+
isContainer: false,
246+
etag: stats.etag,
247+
contentType: outputType,
248+
origin,
249+
resourceUrl,
250+
connegEnabled
251+
});
252+
headers['Vary'] = getVaryHeader(connegEnabled, request.mashlibEnabled);
222253

223-
Object.entries(headers).forEach(([k, v]) => reply.header(k, v));
224-
return reply.send(outputContent);
225-
} catch (e) {
226-
// If not valid JSON-LD, serve as-is
254+
Object.entries(headers).forEach(([k, v]) => reply.header(k, v));
255+
return reply.send(outputContent);
256+
} catch (e) {
257+
// If not valid JSON-LD, serve as-is
258+
}
227259
}
228260
}
229261

0 commit comments

Comments
 (0)