@glossarist/concept-browser 0.7.51 → 0.7.53
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.
- package/cli/index.mjs +32 -0
- package/env.d.ts +15 -0
- package/package.json +12 -2
- package/scripts/__tests__/doctor.test.mjs +147 -0
- package/scripts/doctor.mjs +327 -0
- package/scripts/generate-data.mjs +136 -0
- package/scripts/generate-ontology-data.mjs +3 -3
- package/scripts/generate-ontology-schema.mjs +3 -3
- package/scripts/lib/agents-turtle.mjs +64 -0
- package/scripts/lib/bibliography-turtle.mjs +54 -0
- package/scripts/lib/build-activity-turtle.mjs +92 -0
- package/scripts/lib/build-cache.mjs +70 -0
- package/scripts/lib/dataset-turtle.mjs +79 -0
- package/scripts/lib/turtle-escape.mjs +0 -0
- package/scripts/lib/version-turtle.mjs +56 -0
- package/scripts/lib/vocab-turtle.mjs +64 -0
- package/scripts/normalize-yaml.mjs +99 -0
- package/scripts/sync-concept-model.mjs +86 -0
- package/scripts/validate-shacl.mjs +194 -0
- package/src/App.vue +2 -0
- package/src/__fixtures__/concept-shape.ttl +20 -0
- package/src/__fixtures__/shacl/bad/concept.ttl +7 -0
- package/src/__fixtures__/shacl/empty/.gitkeep +0 -0
- package/src/__fixtures__/shacl/good/concept.ttl +8 -0
- package/src/__tests__/__fixtures__/concepts.ts +221 -0
- package/src/__tests__/adapters/concept-identity.test.ts +76 -0
- package/src/__tests__/components/error-boundary.test.ts +109 -0
- package/src/__tests__/composables/use-dataset-series.test.ts +262 -0
- package/src/__tests__/concept-rdf/agents-emitter.test.ts +110 -0
- package/src/__tests__/concept-rdf/bibliography-emitter.test.ts +159 -0
- package/src/__tests__/concept-rdf/build-activity-emitter.test.ts +119 -0
- package/src/__tests__/concept-rdf/coerce-date.test.ts +97 -0
- package/src/__tests__/concept-rdf/concept-emitter.test.ts +258 -0
- package/src/__tests__/concept-rdf/dataset-emitter.test.ts +224 -0
- package/src/__tests__/concept-rdf/differential.test.ts +96 -0
- package/src/__tests__/concept-rdf/group-emitter.test.ts +109 -0
- package/src/__tests__/concept-rdf/image-variant-emitter.test.ts +135 -0
- package/src/__tests__/concept-rdf/jsonld-writer.test.ts +109 -0
- package/src/__tests__/concept-rdf/nonverbal-rep.test.ts +177 -0
- package/src/__tests__/concept-rdf/property-based.test.ts +179 -0
- package/src/__tests__/concept-rdf/provenance.test.ts +110 -0
- package/src/__tests__/concept-rdf/quad-isomorphism.test.ts +43 -0
- package/src/__tests__/concept-rdf/quad-isomorphism.ts +47 -0
- package/src/__tests__/concept-rdf/rdf-components.test.ts +145 -0
- package/src/__tests__/concept-rdf/rdf-graph.test.ts +115 -0
- package/src/__tests__/concept-rdf/round-trip.test.ts +243 -0
- package/src/__tests__/concept-rdf/scoped-examples.test.ts +126 -0
- package/src/__tests__/concept-rdf/sections-builder.test.ts +94 -0
- package/src/__tests__/concept-rdf/shacl-conformance.test.ts +110 -0
- package/src/__tests__/concept-rdf/shape-consistency.test.ts +138 -0
- package/src/__tests__/concept-rdf/snapshot-generation.test.ts +75 -0
- package/src/__tests__/concept-rdf/table-formula-emitter.test.ts +142 -0
- package/src/__tests__/concept-rdf/turtle-writer.test.ts +114 -0
- package/src/__tests__/concept-rdf/use-rdf-document.test.ts +246 -0
- package/src/__tests__/concept-rdf/version-emitter.test.ts +120 -0
- package/src/__tests__/concept-rdf/vocabulary-ssot.test.ts +100 -0
- package/src/__tests__/concept-rdf-view.test.ts +136 -0
- package/src/__tests__/config/group-renderers.test.ts +35 -0
- package/src/__tests__/config/group-types.test.ts +76 -0
- package/src/__tests__/dataset-style.test.ts +12 -7
- package/src/__tests__/errors/errors.test.ts +142 -0
- package/src/__tests__/format-downloads.test.ts +47 -65
- package/src/__tests__/markdown-lite.test.ts +19 -0
- package/src/__tests__/perf/bundle-layout.test.ts +50 -0
- package/src/__tests__/perf/serialization-perf.test.ts +121 -0
- package/src/__tests__/scripts/agents-turtle.test.ts +61 -0
- package/src/__tests__/scripts/bibliography-turtle.test.ts +59 -0
- package/src/__tests__/scripts/build-activity-turtle.test.ts +75 -0
- package/src/__tests__/scripts/build-cache.test.ts +78 -0
- package/src/__tests__/scripts/build-integration.test.ts +134 -0
- package/src/__tests__/scripts/dataset-turtle.test.ts +94 -0
- package/src/__tests__/scripts/normalize-yaml.test.ts +98 -0
- package/src/__tests__/scripts/stryker-config.test.ts +33 -0
- package/src/__tests__/scripts/turtle-escape.test.ts +63 -0
- package/src/__tests__/scripts/version-turtle.test.ts +72 -0
- package/src/__tests__/scripts/vocab-turtle.test.ts +63 -0
- package/src/__tests__/use-format-registry.test.ts +125 -0
- package/src/__tests__/utils/bcp47.test.ts +166 -0
- package/src/__tests__/utils/color-theme.test.ts +143 -0
- package/src/__tests__/utils/url-safety.test.ts +65 -0
- package/src/__tests__/validate-shacl.test.ts +100 -0
- package/src/adapters/DatasetAdapter.ts +11 -5
- package/src/adapters/GraphDataSource.ts +2 -1
- package/src/adapters/UriRouter.ts +2 -1
- package/src/adapters/concept-identity.ts +69 -0
- package/src/adapters/factory.ts +3 -2
- package/src/adapters/model-bridge.ts +2 -1
- package/src/adapters/non-verbal/glossarist-augment.d.ts +7 -0
- package/src/adapters/non-verbal-resolver.ts +2 -1
- package/src/components/AppSidebar.vue +189 -93
- package/src/components/ConceptDetail.vue +8 -0
- package/src/components/ConceptEditionRail.vue +222 -0
- package/src/components/ConceptRdfView.vue +37 -377
- package/src/components/DatasetSeriesCard.vue +270 -0
- package/src/components/ErrorBoundary.vue +95 -0
- package/src/components/FormatDownloads.vue +17 -13
- package/src/components/HomeSeriesSection.vue +277 -0
- package/src/components/RelationSphere.vue +1672 -0
- package/src/components/SidebarSeriesSection.vue +239 -0
- package/src/components/concept-rdf/RdfInstanceHeader.vue +47 -0
- package/src/components/concept-rdf/RdfInstanceSection.vue +54 -0
- package/src/components/concept-rdf/RdfPrefixLegend.vue +27 -0
- package/src/components/concept-rdf/RdfSourcePanel.vue +72 -0
- package/src/components/concept-rdf/agents-emitter.ts +82 -0
- package/src/components/concept-rdf/bibliography-emitter.ts +83 -0
- package/src/components/concept-rdf/build-activity-emitter.ts +89 -0
- package/src/components/concept-rdf/concept-emitter.ts +443 -0
- package/src/components/concept-rdf/dataset-emitter.ts +95 -0
- package/src/components/concept-rdf/group-emitter.ts +69 -0
- package/src/components/concept-rdf/image-variant-emitter.ts +46 -0
- package/src/components/concept-rdf/jsonld-writer.ts +82 -0
- package/src/components/concept-rdf/predicates.ts +261 -0
- package/src/components/concept-rdf/provenance.ts +80 -0
- package/src/components/concept-rdf/rdf-graph.ts +211 -0
- package/src/components/concept-rdf/rdf-prefixes.ts +23 -0
- package/src/components/concept-rdf/sections-builder.ts +62 -0
- package/src/components/concept-rdf/table-formula-emitter.ts +101 -0
- package/src/components/concept-rdf/turtle-writer.ts +116 -0
- package/src/components/concept-rdf/use-rdf-document.ts +72 -0
- package/src/components/concept-rdf/version-emitter.ts +65 -0
- package/src/components/concept-rdf/vocabulary-emitter.ts +62 -0
- package/src/components/groups/DatasetGroupRenderer.vue +32 -0
- package/src/components/groups/DefaultGroupSidebar.vue +50 -0
- package/src/components/groups/LineageGroupSidebar.vue +75 -0
- package/src/composables/use-color-theme.ts +82 -0
- package/src/composables/use-format-registry.ts +42 -0
- package/src/composables/useDatasetSeries.ts +258 -0
- package/src/composables/useSphereProjection.ts +125 -0
- package/src/config/group-renderers.ts +27 -0
- package/src/config/group-types.ts +92 -0
- package/src/config/types.ts +81 -2
- package/src/config/use-site-config.ts +2 -1
- package/src/errors.ts +136 -0
- package/src/i18n/locales/eng.yml +24 -0
- package/src/i18n/locales/fra.yml +24 -0
- package/src/stores/vocabulary.ts +3 -1
- package/src/style.css +17 -2
- package/src/types/agents-version-turtle.d.ts +27 -0
- package/src/types/bibliography-turtle.d.ts +12 -0
- package/src/types/build-activity-turtle.d.ts +16 -0
- package/src/types/build-cache.d.ts +20 -0
- package/src/types/dataset-turtle.d.ts +32 -0
- package/src/types/normalize-yaml.d.ts +16 -0
- package/src/types/turtle-escape.d.ts +6 -0
- package/src/types/vocab-turtle.d.ts +13 -0
- package/src/utils/asciidoc-lite.ts +11 -6
- package/src/utils/bcp47.ts +141 -0
- package/src/utils/color-theme-integration.ts +11 -0
- package/src/utils/color-theme.ts +129 -0
- package/src/utils/dataset-style.ts +31 -6
- package/src/utils/locale.ts +6 -14
- package/src/utils/markdown-lite.ts +6 -1
- package/src/utils/relation-sphere-styling.ts +63 -0
- package/src/utils/relationship-categories.ts +30 -0
- package/src/utils/url-safety.ts +30 -0
- package/src/views/ConceptView.vue +187 -9
- package/src/views/DatasetView.vue +6 -0
- package/src/views/HomeView.vue +5 -0
- package/vite.config.ts +7 -0
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
2
|
+
import { computed } from 'vue';
|
|
3
|
+
import type { Concept } from 'glossarist';
|
|
4
|
+
import RdfInstanceHeader from './concept-rdf/RdfInstanceHeader.vue';
|
|
5
|
+
import RdfInstanceSection from './concept-rdf/RdfInstanceSection.vue';
|
|
6
|
+
import RdfSourcePanel from './concept-rdf/RdfSourcePanel.vue';
|
|
7
|
+
import ErrorBoundary from './ErrorBoundary.vue';
|
|
8
|
+
import { useRdfDocument } from './concept-rdf/use-rdf-document';
|
|
5
9
|
|
|
6
10
|
const props = defineProps<{
|
|
7
11
|
concept: Concept;
|
|
@@ -9,389 +13,45 @@ const props = defineProps<{
|
|
|
9
13
|
conceptUriValue: string;
|
|
10
14
|
}>();
|
|
11
15
|
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
16
|
+
const { sections, turtle, jsonld, typeChain } = useRdfDocument(
|
|
17
|
+
() => props.concept,
|
|
18
|
+
() => props.conceptUriValue,
|
|
19
|
+
);
|
|
15
20
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
uriCopied.value = true;
|
|
19
|
-
setTimeout(() => { uriCopied.value = false; }, 2000);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function designationClassLabel(type: string): string {
|
|
23
|
-
const map: Record<string, string> = {
|
|
24
|
-
expression: 'gloss:Expression',
|
|
25
|
-
abbreviation: 'gloss:Abbreviation',
|
|
26
|
-
symbol: 'gloss:Symbol',
|
|
27
|
-
letter_symbol: 'gloss:LetterSymbol',
|
|
28
|
-
graphical_symbol: 'gloss:GraphicalSymbol',
|
|
29
|
-
};
|
|
30
|
-
return map[type] ?? 'gloss:Designation';
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function desigSlug(designation: string, index: number): string {
|
|
34
|
-
const slug = designation.replace(/[^a-zA-Z0-9]/g, '_');
|
|
35
|
-
if (/^_+$/.test(slug)) return `d${index}`;
|
|
36
|
-
return slug;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function formatCitation(c: any): string {
|
|
40
|
-
if (!c) return '';
|
|
41
|
-
if (c.source && c.id) return `${c.source} ${c.id}`;
|
|
42
|
-
if (c.ref?.source) {
|
|
43
|
-
const r = c.ref;
|
|
44
|
-
return r.id ? `${r.source} ${r.id}` : r.source;
|
|
45
|
-
}
|
|
46
|
-
return '';
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// ── Instance data extraction ─────────────────────────────────────────────
|
|
50
|
-
|
|
51
|
-
interface PropValue {
|
|
52
|
-
predicate: string;
|
|
53
|
-
values: string[];
|
|
54
|
-
nested?: boolean;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
interface ClassInstance {
|
|
58
|
-
classId: string;
|
|
59
|
-
classLabel: string;
|
|
60
|
-
label: string;
|
|
61
|
-
props: PropValue[];
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function conceptInstance(): ClassInstance {
|
|
65
|
-
const c = props.concept;
|
|
66
|
-
const pv: PropValue[] = [];
|
|
67
|
-
const add = (pred: string, ...vals: string[]) => {
|
|
68
|
-
const filtered = vals.filter(Boolean);
|
|
69
|
-
if (filtered.length) pv.push({ predicate: pred, values: filtered });
|
|
70
|
-
};
|
|
71
|
-
const addNested = (pred: string, ...vals: string[]) => {
|
|
72
|
-
const filtered = vals.filter(Boolean);
|
|
73
|
-
if (filtered.length) pv.push({ predicate: pred, values: filtered, nested: true });
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
add('gloss:identifier', c.id);
|
|
77
|
-
if (c.status) add('gloss:hasStatus', `gloss:status/${c.status}`);
|
|
78
|
-
for (const d of c.domains) addNested('gloss:hasDomain', d.conceptId || d.urn || '');
|
|
79
|
-
for (const s of c.sources) addNested('gloss:hasSource', formatCitation(s.origin));
|
|
80
|
-
for (const d of c.dates) addNested('gloss:hasDate', `${d.type}: ${d.date}`);
|
|
81
|
-
for (const r of c.relatedConcepts) {
|
|
82
|
-
const refLabel = r.content || (r.ref ? `${r.ref.source || ''} ${r.ref.id || ''}`.trim() : '');
|
|
83
|
-
addNested('gloss:hasRelatedConcept', `${r.type}: ${refLabel}`);
|
|
84
|
-
}
|
|
85
|
-
for (const lang of c.languages) addNested('gloss:hasLocalization', `${lang}: ${c.localization(lang)?.primaryDesignation ?? ''}`);
|
|
86
|
-
|
|
87
|
-
return { classId: 'gloss:Concept', classLabel: 'Concept', label: c.id, props: pv };
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function localizedInstance(lc: LocalizedConcept): ClassInstance {
|
|
91
|
-
const pv: PropValue[] = [];
|
|
92
|
-
const add = (pred: string, ...vals: string[]) => {
|
|
93
|
-
const filtered = vals.filter(Boolean);
|
|
94
|
-
if (filtered.length) pv.push({ predicate: pred, values: filtered });
|
|
95
|
-
};
|
|
96
|
-
const addNested = (pred: string, ...vals: string[]) => {
|
|
97
|
-
const filtered = vals.filter(Boolean);
|
|
98
|
-
if (filtered.length) pv.push({ predicate: pred, values: filtered, nested: true });
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
add('dcterms:language', lc.languageCode ?? '');
|
|
102
|
-
if (lc.entryStatus) add('gloss:hasEntryStatus', `gloss:entstatus/${lc.entryStatus}`);
|
|
103
|
-
addNested('gloss:isLocalizationOf', props.conceptUriValue);
|
|
104
|
-
for (const d of lc.terms) addNested(d.normativeStatus === 'preferred' ? 'skosxl:prefLabel' : 'skosxl:altLabel', d.designation);
|
|
105
|
-
for (const d of lc.definitions) if (d.content) addNested('gloss:hasDefinition', d.content);
|
|
106
|
-
for (const n of lc.notes) if (n.content) addNested('gloss:hasNote', n.content);
|
|
107
|
-
for (const e of lc.examples) if (e.content) addNested('gloss:hasExample', e.content);
|
|
108
|
-
for (const s of lc.sources) addNested('gloss:hasSource', formatCitation(s.origin));
|
|
109
|
-
if (lc.domain) add('gloss:domain', lc.domain);
|
|
110
|
-
|
|
111
|
-
return {
|
|
112
|
-
classId: 'gloss:LocalizedConcept',
|
|
113
|
-
classLabel: 'LocalizedConcept',
|
|
114
|
-
label: `${lc.languageCode}: ${lc.primaryDesignation ?? ''}`,
|
|
115
|
-
props: pv,
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function designationInstance(d: Designation): ClassInstance {
|
|
120
|
-
const pv: PropValue[] = [];
|
|
121
|
-
const add = (pred: string, ...vals: string[]) => {
|
|
122
|
-
const filtered = vals.filter(Boolean);
|
|
123
|
-
if (filtered.length) pv.push({ predicate: pred, values: filtered });
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
add('xl:literalForm', `${d.designation}${d.language ? '@' + d.language : ''}`);
|
|
127
|
-
if (d.normativeStatus) add('gloss:normativeStatus', `gloss:norm/${d.normativeStatus}`);
|
|
128
|
-
if (d.geographicalArea) add('gloss:geographicalArea', d.geographicalArea);
|
|
129
|
-
if (d.international) add('gloss:isInternational', 'true');
|
|
130
|
-
if (d.absent) add('gloss:isAbsent', 'true');
|
|
131
|
-
if (d.termType) add('gloss:hasTermType', d.termType);
|
|
132
|
-
for (const p of d.pronunciations ?? []) add('gloss:hasPronunciation', p.content || '');
|
|
133
|
-
|
|
134
|
-
if (d.type === 'expression' || d.type === 'abbreviation') {
|
|
135
|
-
const expr = d as ExpressionType;
|
|
136
|
-
if (expr.prefix) add('gloss:prefix', expr.prefix);
|
|
137
|
-
if (expr.usageInfo) add('gloss:usageInfo', expr.usageInfo);
|
|
138
|
-
if (expr.fieldOfApplication) add('gloss:fieldOfApplication', expr.fieldOfApplication);
|
|
139
|
-
for (const gi of expr.grammarInfo ?? []) {
|
|
140
|
-
const parts: string[] = [];
|
|
141
|
-
if (gi.gender) parts.push(`gender:${gi.gender}`);
|
|
142
|
-
if (gi.number) parts.push(`number:${gi.number}`);
|
|
143
|
-
if (gi.partOfSpeech) parts.push(`pos:${gi.partOfSpeech}`);
|
|
144
|
-
if (parts.length) add('gloss:hasGrammarInfo', parts.join(', '));
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (d.type === 'abbreviation') {
|
|
149
|
-
const abbr = d as AbbreviationType;
|
|
150
|
-
if (abbr.acronym) add('gloss:isAcronym', 'true');
|
|
151
|
-
if (abbr.initialism) add('gloss:isInitialism', 'true');
|
|
152
|
-
if (abbr.truncation) add('gloss:isTruncation', 'true');
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
if (d.type === 'graphical_symbol') {
|
|
156
|
-
const gs = d as GraphicalSymbolType;
|
|
157
|
-
if (gs.text) add('gloss:text', gs.text);
|
|
158
|
-
if (gs.image) add('gloss:image', gs.image);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return {
|
|
162
|
-
classId: designationClassLabel(d.type),
|
|
163
|
-
classLabel: designationClassLabel(d.type).replace('gloss:', ''),
|
|
164
|
-
label: d.designation,
|
|
165
|
-
props: pv,
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// ── Build all sections ──────────────────────────────────────────────
|
|
170
|
-
|
|
171
|
-
const sections = computed<ClassInstance[]>(() => {
|
|
172
|
-
const result: ClassInstance[] = [];
|
|
173
|
-
result.push(conceptInstance());
|
|
174
|
-
|
|
175
|
-
for (const lang of props.concept.languages) {
|
|
176
|
-
const lc = props.concept.localization(lang);
|
|
177
|
-
if (!lc) continue;
|
|
178
|
-
result.push(localizedInstance(lc));
|
|
179
|
-
for (const d of lc.terms) {
|
|
180
|
-
result.push(designationInstance(d));
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return result;
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
// ── Type chain for hierarchy ─────────────────────────────────────────
|
|
188
|
-
|
|
189
|
-
const typeChain = computed(() => {
|
|
190
|
-
const conceptCls = getClass('gloss:Concept');
|
|
191
|
-
if (!conceptCls) return ['owl:Thing', 'skos:Concept', 'gloss:Concept'];
|
|
192
|
-
return ['owl:Thing', ...conceptCls.ancestors, 'gloss:Concept'];
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
// ── Turtle source ────────────────────────────────────────────────────
|
|
196
|
-
|
|
197
|
-
const turtleSource = computed(() => {
|
|
198
|
-
const lines: string[] = [];
|
|
199
|
-
const ind = ' ';
|
|
200
|
-
const c = props.concept;
|
|
201
|
-
const uri = props.conceptUriValue;
|
|
202
|
-
|
|
203
|
-
lines.push('@prefix gloss: <https://www.glossarist.org/ontologies/> .');
|
|
204
|
-
lines.push('@prefix skos: <http://www.w3.org/2004/02/skos/core#> .');
|
|
205
|
-
lines.push('@prefix xl: <http://www.w3.org/2008/05/skos-xl#> .');
|
|
206
|
-
lines.push('@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .');
|
|
207
|
-
lines.push('@prefix dcterms: <http://purl.org/dc/terms/> .');
|
|
208
|
-
lines.push('');
|
|
209
|
-
|
|
210
|
-
lines.push(`<${uri}> a gloss:Concept, skos:Concept ;`);
|
|
211
|
-
lines.push(`${ind}gloss:identifier "${c.id}" ;`);
|
|
212
|
-
if (c.status) lines.push(`${ind}gloss:hasStatus gloss:status/${c.status} ;`);
|
|
213
|
-
for (const lang of c.languages) lines.push(`${ind}gloss:hasLocalization <${uri}/${lang}> ;`);
|
|
214
|
-
for (const r of c.relatedConcepts) {
|
|
215
|
-
lines.push(`${ind}gloss:hasRelatedConcept [`);
|
|
216
|
-
lines.push(`${ind}${ind}gloss:relationshipType gloss:rel/${r.type} ;`);
|
|
217
|
-
if (r.content) lines.push(`${ind}${ind}gloss:relationshipContent "${r.content}" ;`);
|
|
218
|
-
if (r.ref) {
|
|
219
|
-
if (r.ref.source) lines.push(`${ind}${ind}gloss:conceptSource "${r.ref.source}" ;`);
|
|
220
|
-
if (r.ref.id) lines.push(`${ind}${ind}gloss:conceptId "${r.ref.id}" ;`);
|
|
221
|
-
}
|
|
222
|
-
lines.push(`${ind}] ;`);
|
|
223
|
-
}
|
|
224
|
-
lines[lines.length - 1] = lines[lines.length - 1].replace(/ ;$/, ' .');
|
|
225
|
-
|
|
226
|
-
for (const lang of c.languages) {
|
|
227
|
-
const lc = c.localization(lang);
|
|
228
|
-
if (!lc) continue;
|
|
229
|
-
lines.push('');
|
|
230
|
-
lines.push(`<${uri}/${lang}> a gloss:LocalizedConcept, skos:Concept ;`);
|
|
231
|
-
lines.push(`${ind}dcterms:language "${lang}" ;`);
|
|
232
|
-
lines.push(`${ind}gloss:isLocalizationOf <${uri}> ;`);
|
|
233
|
-
if (lc.entryStatus) lines.push(`${ind}gloss:hasEntryStatus gloss:entstatus/${lc.entryStatus} ;`);
|
|
234
|
-
for (let di = 0; di < lc.terms.length; di++) {
|
|
235
|
-
const d = lc.terms[di];
|
|
236
|
-
const normPrefix = d.normativeStatus === 'preferred' ? 'skosxl:prefLabel' : 'skosxl:altLabel';
|
|
237
|
-
lines.push(`${ind}${normPrefix} <${uri}/${lang}/desig/${desigSlug(d.designation, di)}> ;`);
|
|
238
|
-
}
|
|
239
|
-
for (const def of lc.definitions) {
|
|
240
|
-
if (def.content) lines.push(`${ind}gloss:hasDefinition [ rdf:value "${def.content}" ] ;`);
|
|
241
|
-
}
|
|
242
|
-
lines[lines.length - 1] = lines[lines.length - 1].replace(/ ;$/, ' .');
|
|
243
|
-
|
|
244
|
-
for (let di = 0; di < lc.terms.length; di++) {
|
|
245
|
-
const d = lc.terms[di];
|
|
246
|
-
const desigUri = `${uri}/${lang}/desig/${desigSlug(d.designation, di)}`;
|
|
247
|
-
const dc = designationClassLabel(d.type);
|
|
248
|
-
lines.push('');
|
|
249
|
-
lines.push(`<${desigUri}> a ${dc}, xl:Label ;`);
|
|
250
|
-
lines.push(`${ind}xl:literalForm "${d.designation}"@${lang} ;`);
|
|
251
|
-
if (d.normativeStatus) lines.push(`${ind}gloss:normativeStatus gloss:norm/${d.normativeStatus} ;`);
|
|
252
|
-
lines[lines.length - 1] = lines[lines.length - 1].replace(/ ;$/, ' .');
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
return lines.join('\n');
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
// ── JSON-LD source ──────────────────────────────────────────────────
|
|
260
|
-
|
|
261
|
-
const jsonldSource = computed(() => {
|
|
262
|
-
const c = props.concept;
|
|
263
|
-
const uri = props.conceptUriValue;
|
|
264
|
-
|
|
265
|
-
const doc: any = {
|
|
266
|
-
'@context': {
|
|
267
|
-
gloss: 'https://www.glossarist.org/ontologies/',
|
|
268
|
-
skos: 'http://www.w3.org/2004/02/skos/core#',
|
|
269
|
-
xl: 'http://www.w3.org/2008/05/skos-xl#',
|
|
270
|
-
dcterms: 'http://purl.org/dc/terms/',
|
|
271
|
-
},
|
|
272
|
-
'@graph': [],
|
|
273
|
-
};
|
|
274
|
-
|
|
275
|
-
const conceptNode: any = {
|
|
276
|
-
'@id': uri,
|
|
277
|
-
'@type': ['gloss:Concept', 'skos:Concept'],
|
|
278
|
-
'gloss:identifier': c.id,
|
|
279
|
-
};
|
|
280
|
-
if (c.status) conceptNode['gloss:hasStatus'] = { '@id': `gloss:status/${c.status}` };
|
|
281
|
-
conceptNode['gloss:hasLocalization'] = c.languages.map(l => ({ '@id': `${uri}/${l}` }));
|
|
282
|
-
doc['@graph'].push(conceptNode);
|
|
283
|
-
|
|
284
|
-
for (const lang of c.languages) {
|
|
285
|
-
const lc = c.localization(lang);
|
|
286
|
-
if (!lc) continue;
|
|
287
|
-
const lcNode: any = {
|
|
288
|
-
'@id': `${uri}/${lang}`,
|
|
289
|
-
'@type': ['gloss:LocalizedConcept', 'skos:Concept'],
|
|
290
|
-
'dcterms:language': lang,
|
|
291
|
-
'gloss:isLocalizationOf': { '@id': uri },
|
|
292
|
-
};
|
|
293
|
-
if (lc.entryStatus) lcNode['gloss:hasEntryStatus'] = { '@id': `gloss:entstatus/${lc.entryStatus}` };
|
|
294
|
-
for (let di = 0; di < lc.terms.length; di++) {
|
|
295
|
-
const d = lc.terms[di];
|
|
296
|
-
const key = d.normativeStatus === 'preferred' ? 'skosxl:prefLabel' : 'skosxl:altLabel';
|
|
297
|
-
lcNode[key] = lcNode[key] || [];
|
|
298
|
-
lcNode[key].push({ '@id': `${uri}/${lang}/desig/${desigSlug(d.designation, di)}` });
|
|
299
|
-
}
|
|
300
|
-
doc['@graph'].push(lcNode);
|
|
301
|
-
|
|
302
|
-
for (let di = 0; di < lc.terms.length; di++) {
|
|
303
|
-
const d = lc.terms[di];
|
|
304
|
-
const desigUri = `${uri}/${lang}/desig/${desigSlug(d.designation, di)}`;
|
|
305
|
-
const desigNode: any = {
|
|
306
|
-
'@id': desigUri,
|
|
307
|
-
'@type': [designationClassLabel(d.type), 'xl:Label'],
|
|
308
|
-
'xl:literalForm': { '@value': d.designation, '@language': lang },
|
|
309
|
-
};
|
|
310
|
-
if (d.normativeStatus) desigNode['gloss:normativeStatus'] = { '@id': `gloss:norm/${d.normativeStatus}` };
|
|
311
|
-
doc['@graph'].push(desigNode);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
return JSON.stringify(doc, null, 2);
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
const rdfSource = computed(() => rdfFormat.value === 'turtle' ? turtleSource.value : jsonldSource.value);
|
|
21
|
+
const resourceCount = computed(() => sections.value.length);
|
|
22
|
+
const conceptId = computed(() => props.concept.id);
|
|
319
23
|
</script>
|
|
320
24
|
|
|
321
25
|
<template>
|
|
322
|
-
<
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
<div class="text-[10px] uppercase tracking-widest text-ink-300 font-medium mb-2">RDF Instance</div>
|
|
328
|
-
<div class="flex items-center gap-2 flex-wrap">
|
|
329
|
-
<code class="text-sm font-mono text-ink-700 break-all">{{ conceptUriValue }}</code>
|
|
330
|
-
<button @click="copyUri" class="p-1.5 rounded text-ink-300 hover:text-ink-600 hover:bg-ink-50 transition-colors flex-shrink-0" :title="uriCopied ? 'Copied!' : 'Copy URI'">
|
|
331
|
-
<svg v-if="!uriCopied" class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10a2 2 0 01-2-2v-1m6 4v-3a2 2 0 00-2-2H8"/></svg>
|
|
332
|
-
<svg v-else class="w-3.5 h-3.5 text-emerald-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>
|
|
333
|
-
</button>
|
|
334
|
-
</div>
|
|
335
|
-
<div class="flex gap-1.5 mt-2.5">
|
|
336
|
-
<router-link to="/ontology/class/gloss-Concept" class="inline-flex items-center px-2 py-0.5 rounded text-[11px] font-medium bg-blue-50 text-blue-700 border border-blue-100 hover:bg-blue-100 transition-colors">gloss:Concept</router-link>
|
|
337
|
-
<router-link to="/ontology/class/gloss-Concept" class="inline-flex items-center px-2 py-0.5 rounded text-[11px] font-medium bg-emerald-50 text-emerald-700 border border-emerald-100 hover:bg-emerald-100 transition-colors">skos:Concept</router-link>
|
|
338
|
-
</div>
|
|
339
|
-
</div>
|
|
340
|
-
</div>
|
|
341
|
-
|
|
342
|
-
<!-- Mini hierarchy -->
|
|
343
|
-
<div class="mt-4 pt-3 border-t border-ink-100/60">
|
|
344
|
-
<div class="flex items-center gap-1.5 flex-wrap text-xs text-ink-400">
|
|
345
|
-
<template v-for="(t, i) in typeChain" :key="i">
|
|
346
|
-
<span v-if="i > 0" class="text-ink-200 mx-0.5">→</span>
|
|
347
|
-
<code class="text-[11px] text-ink-400">{{ t }}</code>
|
|
348
|
-
</template>
|
|
349
|
-
<span class="text-ink-200 mx-0.5">→</span>
|
|
350
|
-
<code class="text-[11px] text-ink-700 font-semibold bg-ink-50 px-1.5 py-0.5 rounded">{{ concept.id }}</code>
|
|
26
|
+
<ErrorBoundary title="RDF view failed" :retry-key="conceptUriValue">
|
|
27
|
+
<div class="space-y-6">
|
|
28
|
+
<div class="card p-5">
|
|
29
|
+
<div class="flex items-start justify-between gap-3">
|
|
30
|
+
<RdfInstanceHeader :uri="conceptUriValue" :concept-id="conceptId" />
|
|
351
31
|
</div>
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
<div class="flex items-center gap-2 mb-3">
|
|
358
|
-
<div class="w-1 h-4 rounded-full" :class="section.classId === 'gloss:Concept' ? 'bg-blue-500' : section.classId === 'gloss:LocalizedConcept' ? 'bg-emerald-500' : 'bg-amber-500'"></div>
|
|
359
|
-
<router-link :to="`/ontology/class/${section.classId.replace(/:/g, '-')}`" class="text-xs font-semibold text-ink-700 hover:text-blue-600 transition-colors">{{ section.classId }}</router-link>
|
|
360
|
-
<span class="text-xs text-ink-400">·</span>
|
|
361
|
-
<span class="text-xs text-ink-500">{{ section.label }}</span>
|
|
362
|
-
</div>
|
|
363
|
-
|
|
364
|
-
<div class="space-y-1.5">
|
|
365
|
-
<div v-for="prop in section.props" :key="prop.predicate" class="grid grid-cols-[160px_1fr] gap-x-3 gap-y-0.5 py-1.5 border-b border-ink-100/30 last:border-0">
|
|
366
|
-
<code class="text-xs text-blue-600 font-medium leading-relaxed self-start pt-0.5">{{ prop.predicate }}</code>
|
|
367
|
-
<div class="flex flex-col gap-0.5">
|
|
368
|
-
<template v-for="(val, vi) in prop.values" :key="vi">
|
|
369
|
-
<span v-if="prop.nested" class="text-xs text-ink-600 bg-ink-50/60 px-2 py-1 rounded border-l-2 border-ink-200 leading-relaxed break-words">{{ val }}</span>
|
|
370
|
-
<span v-else class="text-xs text-ink-600 leading-relaxed break-words">{{ val }}</span>
|
|
32
|
+
<div class="mt-4 pt-3 border-t border-ink-100/60">
|
|
33
|
+
<div class="flex items-center gap-1.5 flex-wrap text-xs text-ink-400">
|
|
34
|
+
<template v-for="(t, i) in typeChain" :key="i">
|
|
35
|
+
<span v-if="i > 0" class="text-ink-200 mx-0.5">→</span>
|
|
36
|
+
<code class="text-[11px] text-ink-400">{{ t }}</code>
|
|
371
37
|
</template>
|
|
38
|
+
<span class="text-ink-200 mx-0.5">→</span>
|
|
39
|
+
<code class="text-[11px] text-ink-700 font-semibold bg-ink-50 px-1.5 py-0.5 rounded">{{ concept.id }}</code>
|
|
372
40
|
</div>
|
|
373
41
|
</div>
|
|
374
42
|
</div>
|
|
375
|
-
</div>
|
|
376
43
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
</select>
|
|
389
|
-
<span class="text-[10px] text-ink-300">{{ sections.length }} resources</span>
|
|
390
|
-
</div>
|
|
391
|
-
</button>
|
|
392
|
-
<div v-if="showSource" class="border-t border-ink-100/60">
|
|
393
|
-
<pre class="p-4 text-xs font-mono text-ink-700 bg-ink-50/30 overflow-x-auto leading-relaxed max-h-[600px] overflow-y-auto">{{ rdfSource }}</pre>
|
|
394
|
-
</div>
|
|
44
|
+
<RdfInstanceSection
|
|
45
|
+
v-for="(section, si) in sections"
|
|
46
|
+
:key="si"
|
|
47
|
+
:section="section"
|
|
48
|
+
/>
|
|
49
|
+
|
|
50
|
+
<RdfSourcePanel
|
|
51
|
+
:turtle="turtle"
|
|
52
|
+
:jsonld="jsonld"
|
|
53
|
+
:resource-count="resourceCount"
|
|
54
|
+
/>
|
|
395
55
|
</div>
|
|
396
|
-
</
|
|
56
|
+
</ErrorBoundary>
|
|
397
57
|
</template>
|