@matdata/yasr 5.20.3 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Standalone helpers for the Response plugin's Ctrl+Click URI exploration feature.
3
+ *
4
+ * These functions are intentionally free of any DOM or CodeMirror dependencies so
5
+ * that they can be unit-tested in isolation.
6
+ */
7
+
8
+ // Matches angle-bracket IRIs as they appear in Turtle / N-Triples / TriG, e.g. `<http://example.org/foo>`
9
+ const ANGLE_IRI_RE = /<([^<>\s"{}|\\^`]+)>/g;
10
+ // Matches bare or quoted URIs as they appear in JSON / XML / CSV responses, e.g. `"http://example.org/foo"`
11
+ const BARE_URI_RE = /(?:https?|urn|ftp|mailto):[^\s<>"'`{}|\\^[\]]+/g;
12
+ // Matches Turtle/SPARQL prefix declarations such as `@prefix ex: <...> .` and `PREFIX ex: <...>`.
13
+ const PREFIX_DECL_RE = /^\s*(?:@prefix|PREFIX)\s+(\w*):\s*<[^>]+>\s*\.?\s*$/i;
14
+
15
+ /**
16
+ * Remove trailing punctuation that is commonly adjacent to a URI in a serialized
17
+ * response (e.g. the `.` terminating a Turtle statement) but is not part of the URI.
18
+ */
19
+ function sanitizeUri(uri: string): string {
20
+ return uri.replace(/[.,;)\]]+$/, "").trim();
21
+ }
22
+
23
+ /**
24
+ * Find the URI located at the given character offset within a serialized SPARQL response.
25
+ *
26
+ * Angle-bracket IRIs (Turtle/N-Triples style) take precedence over bare URIs so that a
27
+ * click inside `<http://…>` returns the full IRI rather than the embedded `http://…` run.
28
+ *
29
+ * @param text The full response text as shown in the response view.
30
+ * @param offset The character offset (0-based) of the click within `text`.
31
+ * @returns The URI at that offset, or `undefined` when the offset is not on a URI.
32
+ */
33
+ export function extractUriAtOffset(text: string, offset: number): string | undefined {
34
+ if (!text || offset < 0 || offset >= text.length) return undefined;
35
+
36
+ let match: RegExpExecArray | null;
37
+
38
+ ANGLE_IRI_RE.lastIndex = 0;
39
+ while ((match = ANGLE_IRI_RE.exec(text)) !== null) {
40
+ const start = match.index;
41
+ const end = start + match[0].length;
42
+ if (offset >= start && offset <= end) {
43
+ const uri = sanitizeUri(match[1]);
44
+ return uri.length > 0 ? uri : undefined;
45
+ }
46
+ }
47
+
48
+ BARE_URI_RE.lastIndex = 0;
49
+ while ((match = BARE_URI_RE.exec(text)) !== null) {
50
+ const start = match.index;
51
+ const end = start + match[0].length;
52
+ if (offset >= start && offset <= end) {
53
+ const uri = sanitizeUri(match[0]);
54
+ return uri.length > 0 ? uri : undefined;
55
+ }
56
+ }
57
+
58
+ return undefined;
59
+ }
60
+
61
+ /**
62
+ * Build a CONSTRUCT query that retrieves all triples where the given URI is the subject.
63
+ *
64
+ * @param uri The URI to look up as a subject.
65
+ * @returns A SPARQL `CONSTRUCT` query string.
66
+ */
67
+ export function buildSubjectOfQuery(uri: string): string {
68
+ return `CONSTRUCT { <${uri}> ?p ?o } WHERE { <${uri}> ?p ?o }`;
69
+ }
70
+
71
+ /**
72
+ * Build a CONSTRUCT query that retrieves all triples where the given URI is the object.
73
+ *
74
+ * @param uri The URI to look up as an object.
75
+ * @returns A SPARQL `CONSTRUCT` query string.
76
+ */
77
+ export function buildObjectOfQuery(uri: string): string {
78
+ return `CONSTRUCT { ?s ?p <${uri}> } WHERE { ?s ?p <${uri}> } LIMIT 1000`;
79
+ }
80
+
81
+ /**
82
+ * Remove duplicate prefix declarations from a text block.
83
+ *
84
+ * Prefixes are considered duplicates by label (case-insensitive), and can be
85
+ * deduplicated against already-rendered content in the response viewer.
86
+ */
87
+ export function stripDuplicatePrefixDeclarations(text: string, existingText = ""): string {
88
+ if (!text) return text;
89
+
90
+ const seenLabels = new Set<string>();
91
+ const addSeenLabels = (source: string) => {
92
+ for (const line of source.split("\n")) {
93
+ const match = line.match(PREFIX_DECL_RE);
94
+ if (!match) continue;
95
+ seenLabels.add(match[1].toLowerCase());
96
+ }
97
+ };
98
+
99
+ addSeenLabels(existingText);
100
+
101
+ const deduplicatedLines: string[] = [];
102
+ for (const line of text.split("\n")) {
103
+ const match = line.match(PREFIX_DECL_RE);
104
+ if (!match) {
105
+ deduplicatedLines.push(line);
106
+ continue;
107
+ }
108
+
109
+ const label = match[1].toLowerCase();
110
+ if (seenLabels.has(label)) continue;
111
+
112
+ seenLabels.add(label);
113
+ deduplicatedLines.push(line);
114
+ }
115
+
116
+ return deduplicatedLines.join("\n");
117
+ }