@glossarist/concept-browser 0.7.134 → 0.7.136
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/README.md +20 -0
- package/data/concept-model/SOURCE.json +2 -3
- package/data/concept-model/glossarist.context.jsonld +15 -0
- package/data/concept-model/glossarist.ttl +86 -1
- package/data/concept-model/shapes/glossarist.shacl.ttl +169 -1
- package/package.json +2 -2
- package/scripts/__tests__/doctor.test.ts +45 -5
- package/scripts/doctor.js +64 -34
- package/scripts/doctor.ts +83 -35
- package/scripts/fetch-datasets.js +1 -0
- package/scripts/generate-data.js +73 -231
- package/scripts/generate-data.ts +94 -41
- package/scripts/lib/concept-uri.ts +11 -0
- package/scripts/load-site-config.js +1 -0
- package/scripts/load-site-config.ts +1 -0
- package/scripts/process-about-pages.js +1 -0
- package/src/__tests__/scripts/concept-csv.test.ts +106 -0
- package/src/composables/use-format-registry.ts +1 -0
- package/src/config/types.ts +1 -0
- package/src/data/ontology-schema.json +374 -8
- package/scripts/lib/concept-formats.ts +0 -206
- package/src/__tests__/concept-formats.test.ts +0 -111
- package/src/utils/concept-formats.ts +0 -123
package/README.md
CHANGED
|
@@ -425,6 +425,26 @@ data:
|
|
|
425
425
|
|
|
426
426
|
Or use a Glossarist GCR package — set `gcr_package` in your dataset config and the CLI downloads it automatically.
|
|
427
427
|
|
|
428
|
+
## Bulk exports
|
|
429
|
+
|
|
430
|
+
The build emits single-file aggregate distributions per dataset into `public/data/{id}/`:
|
|
431
|
+
|
|
432
|
+
| File | Format | Contents |
|
|
433
|
+
|------|--------|----------|
|
|
434
|
+
| `{id}.csv` | CSV (UTF-8 BOM + CRLF, RFC 4180) | One row per concept × language — termid, uri, status, section, language, term, alt terms, definition, notes, examples, sources, source links. Opens directly in Excel. |
|
|
435
|
+
| `{id}.jsonld` | SKOS JSON-LD | All concepts in one `@graph` document |
|
|
436
|
+
| `{id}.ttl` | Turtle | dcat:Dataset + ConceptScheme metadata |
|
|
437
|
+
|
|
438
|
+
Files from the dataset's GCR `compiled/` directory are also copied and listed. The dataset page's **Downloads** card offers everything found, unless you restrict it with `bulk_exports` in `site-config.yml`:
|
|
439
|
+
|
|
440
|
+
```yaml
|
|
441
|
+
bulk_exports:
|
|
442
|
+
- csv
|
|
443
|
+
- ttl
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
Values: `ttl`, `jsonld`, `csv`, `tbx`, `jsonl`, `yaml`. Absent or empty = offer all available.
|
|
447
|
+
|
|
428
448
|
## Deployment
|
|
429
449
|
|
|
430
450
|
The build outputs a static site to `dist/`. Deploy it anywhere:
|
|
@@ -34,6 +34,9 @@
|
|
|
34
34
|
"ConceptRef": "gloss:ConceptRef",
|
|
35
35
|
"Reference": "gloss:Reference",
|
|
36
36
|
"RelatedConcept": "gloss:RelatedConcept",
|
|
37
|
+
"PartitiveHyperedge": "gloss:PartitiveHyperedge",
|
|
38
|
+
"PartitiveEnumeration": "gloss:PartitiveEnumeration",
|
|
39
|
+
"PluralityMarker": "gloss:PluralityMarker",
|
|
37
40
|
"DesignationRelationship": "gloss:DesignationRelationship",
|
|
38
41
|
"ConceptDate": "gloss:ConceptDate",
|
|
39
42
|
"NonVerbalRepresentation": "gloss:NonVerbalRepresentation",
|
|
@@ -54,6 +57,7 @@
|
|
|
54
57
|
"isLocalizationOf": { "@id": "gloss:isLocalizationOf", "@type": "@id" },
|
|
55
58
|
"hasStatus": { "@id": "gloss:hasStatus", "@type": "@id" },
|
|
56
59
|
"hasDomain": { "@id": "gloss:hasDomain", "@type": "@id" },
|
|
60
|
+
"tag": { "@id": "gloss:tag", "@type": "xsd:string" },
|
|
57
61
|
"hasRelatedConcept": { "@id": "gloss:hasRelatedConcept", "@type": "@id" },
|
|
58
62
|
"hasDate": { "@id": "gloss:hasDate", "@type": "@id" },
|
|
59
63
|
"hasSource": { "@id": "gloss:hasSource", "@type": "@id" },
|
|
@@ -112,8 +116,18 @@
|
|
|
112
116
|
"relationshipType": { "@id": "gloss:relationshipType", "@type": "@id" },
|
|
113
117
|
"relationshipContent": { "@id": "gloss:relationshipContent", "@type": "xsd:string" },
|
|
114
118
|
"relationshipRef": { "@id": "gloss:relationshipRef", "@type": "@id" },
|
|
119
|
+
"relatedConceptBroader": { "@id": "gloss:relatedConceptBroader", "@type": "@id" },
|
|
120
|
+
"relatedConceptNarrower": { "@id": "gloss:relatedConceptNarrower", "@type": "@id" },
|
|
121
|
+
|
|
122
|
+
"hasPartitiveHyperedge": { "@id": "gloss:hasPartitiveHyperedge", "@type": "@id" },
|
|
123
|
+
"comprehensive": { "@id": "gloss:comprehensive", "@type": "@id" },
|
|
124
|
+
"hasPart": { "@id": "gloss:hasPart", "@type": "@id" },
|
|
125
|
+
"enumeration": { "@id": "gloss:enumeration", "@type": "@id" },
|
|
126
|
+
"hasPluralityMarker": { "@id": "gloss:hasPluralityMarker", "@type": "@id" },
|
|
127
|
+
"hyperedgeContent": { "@id": "gloss:hyperedgeContent", "@type": "xsd:string" },
|
|
115
128
|
|
|
116
129
|
"refType": { "@id": "gloss:refType", "@type": "xsd:string" },
|
|
130
|
+
"source": { "@id": "gloss:source", "@type": "xsd:string" },
|
|
117
131
|
"urn": { "@id": "gloss:urn", "@type": "xsd:anyURI" },
|
|
118
132
|
"term": { "@id": "gloss:term", "@type": "xsd:string" },
|
|
119
133
|
"refId": { "@id": "gloss:refId", "@type": "xsd:string" },
|
|
@@ -125,6 +139,7 @@
|
|
|
125
139
|
"sourceId": { "@id": "gloss:sourceId", "@type": "xsd:string" },
|
|
126
140
|
"sourceOrigin": { "@id": "gloss:sourceOrigin", "@type": "@id" },
|
|
127
141
|
"modification": { "@id": "gloss:modification", "@type": "xsd:string" },
|
|
142
|
+
"sourcedFrom": { "@id": "gloss:sourcedFrom", "@type": "@id" },
|
|
128
143
|
|
|
129
144
|
"hasCitationRef": { "@id": "gloss:hasCitationRef", "@type": "@id" },
|
|
130
145
|
"hasCitationLocality": { "@id": "gloss:hasCitationLocality", "@type": "@id" },
|
|
@@ -193,6 +193,44 @@ gloss:RelatedConcept a owl:Class ;
|
|
|
193
193
|
rdfs:label "Related Concept"@en ;
|
|
194
194
|
rdfs:comment """
|
|
195
195
|
A concept with a typed relationship (deprecates, broader, equivalent, etc.).
|
|
196
|
+
Binary: one source concept, one target concept via a ConceptRef.
|
|
197
|
+
For one-to-many partitive decompositions with plurality markers or
|
|
198
|
+
open/closed enumeration, use gloss:PartitiveHyperedge.
|
|
199
|
+
"""@en .
|
|
200
|
+
|
|
201
|
+
gloss:PartitiveHyperedge a owl:Class ;
|
|
202
|
+
rdfs:label "Partitive Hyperedge"@en ;
|
|
203
|
+
rdfs:comment """
|
|
204
|
+
A one-to-many partitive decomposition: one comprehensive (whole)
|
|
205
|
+
concept related to one or more parts as a SINGLE relationship.
|
|
206
|
+
|
|
207
|
+
Used when the source diagram depicts a partitive rake whose
|
|
208
|
+
membership or notation is jointly significant — i.e. the
|
|
209
|
+
decomposition cannot be faithfully split into binary
|
|
210
|
+
RelatedConcept edges without losing invariants (which
|
|
211
|
+
comprehensive a part belongs to, the plurality of the rake,
|
|
212
|
+
the completeness of the enumeration).
|
|
213
|
+
|
|
214
|
+
Independent of gloss:hasRelatedConcept. A concept may have both
|
|
215
|
+
binary related entries and partitive hyperedges.
|
|
216
|
+
"""@en .
|
|
217
|
+
|
|
218
|
+
gloss:PartitiveEnumeration a owl:Class ;
|
|
219
|
+
rdfs:label "Partitive Enumeration"@en ;
|
|
220
|
+
rdfs:comment """
|
|
221
|
+
Whether the parts array of a PartitiveHyperedge constitutes the
|
|
222
|
+
COMPLETE decomposition of the comprehensive (closed) or only a
|
|
223
|
+
PARTIAL one (open). Orthogonal to PluralityMarker.
|
|
224
|
+
"""@en .
|
|
225
|
+
|
|
226
|
+
gloss:PluralityMarker a owl:Class ;
|
|
227
|
+
rdfs:label "Plurality Marker"@en ;
|
|
228
|
+
rdfs:comment """
|
|
229
|
+
Diagram notation flags that travel with a PartitiveHyperedge so
|
|
230
|
+
the rendering layer can reproduce the source diagram's notation
|
|
231
|
+
(close-set double line, dashed line). Originates from VIM
|
|
232
|
+
(International Vocabulary of Metrology) concept-relationship
|
|
233
|
+
diagrams. Orthogonal to PartitiveEnumeration.
|
|
196
234
|
"""@en .
|
|
197
235
|
|
|
198
236
|
gloss:DesignationRelationship a owl:Class ;
|
|
@@ -312,7 +350,37 @@ gloss:hasRelatedConcept a owl:ObjectProperty ;
|
|
|
312
350
|
] ;
|
|
313
351
|
rdfs:range gloss:RelatedConcept ;
|
|
314
352
|
rdfs:label "has related concept"@en ;
|
|
315
|
-
rdfs:comment "DATA: Typed semantic relationship — applicable at concept and localization levels."@en .
|
|
353
|
+
rdfs:comment "DATA: Typed binary semantic relationship — applicable at concept and localization levels."@en .
|
|
354
|
+
|
|
355
|
+
gloss:hasPartitiveHyperedge a owl:ObjectProperty ;
|
|
356
|
+
rdfs:domain gloss:Concept ;
|
|
357
|
+
rdfs:range gloss:PartitiveHyperedge ;
|
|
358
|
+
rdfs:label "has partitive hyperedge"@en ;
|
|
359
|
+
rdfs:comment "DATA: One-to-many partitive decomposition on a managed concept. Concept-level only — hyperedges are not localized."@en .
|
|
360
|
+
|
|
361
|
+
gloss:comprehensive a owl:ObjectProperty ;
|
|
362
|
+
rdfs:domain gloss:PartitiveHyperedge ;
|
|
363
|
+
rdfs:range gloss:ConceptRef ;
|
|
364
|
+
rdfs:label "comprehensive"@en ;
|
|
365
|
+
rdfs:comment "The whole concept that owns the parts in a partitive hyperedge. Exactly one per hyperedge."@en .
|
|
366
|
+
|
|
367
|
+
gloss:hasPart a owl:ObjectProperty ;
|
|
368
|
+
rdfs:domain gloss:PartitiveHyperedge ;
|
|
369
|
+
rdfs:range gloss:ConceptRef ;
|
|
370
|
+
rdfs:label "has part"@en ;
|
|
371
|
+
rdfs:comment "A partitive member of a hyperedge. A hyperedge has one or more parts."@en .
|
|
372
|
+
|
|
373
|
+
gloss:enumeration a owl:ObjectProperty ;
|
|
374
|
+
rdfs:domain gloss:PartitiveHyperedge ;
|
|
375
|
+
rdfs:range gloss:PartitiveEnumeration ;
|
|
376
|
+
rdfs:label "enumeration"@en ;
|
|
377
|
+
rdfs:comment "Whether the encoded parts constitute a complete (closed) or partial (open) decomposition."@en .
|
|
378
|
+
|
|
379
|
+
gloss:hasPluralityMarker a owl:ObjectProperty ;
|
|
380
|
+
rdfs:domain gloss:PartitiveHyperedge ;
|
|
381
|
+
rdfs:range gloss:PluralityMarker ;
|
|
382
|
+
rdfs:label "has plurality marker"@en ;
|
|
383
|
+
rdfs:comment "Diagram notation flag on a hyperedge (double for close-set double line, dashed for uncertainty)."@en .
|
|
316
384
|
|
|
317
385
|
gloss:hasDate a owl:ObjectProperty ;
|
|
318
386
|
rdfs:domain [
|
|
@@ -632,6 +700,12 @@ gloss:relationshipContent a owl:DatatypeProperty ;
|
|
|
632
700
|
rdfs:label "relationship content"@en ;
|
|
633
701
|
rdfs:comment "Free-text description of the relationship."@en .
|
|
634
702
|
|
|
703
|
+
gloss:hyperedgeContent a owl:DatatypeProperty ;
|
|
704
|
+
rdfs:domain gloss:PartitiveHyperedge ;
|
|
705
|
+
rdfs:range xsd:string ;
|
|
706
|
+
rdfs:label "hyperedge content"@en ;
|
|
707
|
+
rdfs:comment "Free-form text associated with a PartitiveHyperedge, e.g. a diagram caption or label."@en .
|
|
708
|
+
|
|
635
709
|
gloss:relationshipRef a owl:ObjectProperty ;
|
|
636
710
|
rdfs:domain gloss:RelatedConcept ;
|
|
637
711
|
rdfs:range gloss:ConceptRef ;
|
|
@@ -720,6 +794,17 @@ gloss:modification a owl:DatatypeProperty ;
|
|
|
720
794
|
rdfs:label "modification"@en ;
|
|
721
795
|
rdfs:comment "How the concept was modified from the source."@en .
|
|
722
796
|
|
|
797
|
+
gloss:sourcedFrom a owl:ObjectProperty ;
|
|
798
|
+
rdfs:domain gloss:ConceptSource ;
|
|
799
|
+
rdfs:range gloss:Citation ;
|
|
800
|
+
rdfs:label "sourced from"@en ;
|
|
801
|
+
rdfs:comment """
|
|
802
|
+
Citations to the documents that this source was itself sourced
|
|
803
|
+
from. Only valid on lineage sources; authoritative sources are
|
|
804
|
+
terminal. When present, status and modification describe how
|
|
805
|
+
origin relates to these upstream sources.
|
|
806
|
+
"""@en .
|
|
807
|
+
|
|
723
808
|
# ── Citation resolution ────────────────────────────────────────────────────
|
|
724
809
|
#
|
|
725
810
|
# An inline `{{cite:id}}` mention in a concept's text
|
|
@@ -43,13 +43,17 @@ gloss:ConceptShape a sh:NodeShape ;
|
|
|
43
43
|
sh:property [
|
|
44
44
|
sh:path gloss:hasStatus ;
|
|
45
45
|
sh:class skos:Concept ;
|
|
46
|
-
sh:valuesFrom <https://www.glossarist.org/ontologies/
|
|
46
|
+
sh:valuesFrom <https://www.glossarist.org/ontologies/status> ;
|
|
47
47
|
sh:maxCount 1 ;
|
|
48
48
|
] ;
|
|
49
49
|
sh:property [
|
|
50
50
|
sh:path gloss:hasRelatedConcept ;
|
|
51
51
|
sh:class gloss:RelatedConcept ;
|
|
52
52
|
] ;
|
|
53
|
+
sh:property [
|
|
54
|
+
sh:path gloss:hasPartitiveHyperedge ;
|
|
55
|
+
sh:class gloss:PartitiveHyperedge ;
|
|
56
|
+
] ;
|
|
53
57
|
sh:property [
|
|
54
58
|
sh:path gloss:hasDate ;
|
|
55
59
|
sh:class gloss:ConceptDate ;
|
|
@@ -391,6 +395,24 @@ gloss:ConceptSourceShape a sh:NodeShape ;
|
|
|
391
395
|
sh:path gloss:modification ;
|
|
392
396
|
sh:datatype xsd:string ;
|
|
393
397
|
sh:maxCount 1 ;
|
|
398
|
+
] ;
|
|
399
|
+
sh:property [
|
|
400
|
+
sh:path gloss:sourcedFrom ;
|
|
401
|
+
sh:class gloss:Citation ;
|
|
402
|
+
] ;
|
|
403
|
+
sh:sparql [
|
|
404
|
+
sh:message "sourced_from is only allowed on sources of type lineage"@en ;
|
|
405
|
+
sh:select """
|
|
406
|
+
PREFIX gloss: <https://www.glossarist.org/ontologies/>
|
|
407
|
+
PREFIX srctype: <https://www.glossarist.org/ontologies/srctype/>
|
|
408
|
+
SELECT $this
|
|
409
|
+
WHERE {
|
|
410
|
+
$this gloss:sourcedFrom ?target .
|
|
411
|
+
FILTER NOT EXISTS {
|
|
412
|
+
$this gloss:sourceType srctype:lineage .
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
""" ;
|
|
394
416
|
] .
|
|
395
417
|
|
|
396
418
|
gloss:ConceptDateShape a sh:NodeShape ;
|
|
@@ -699,6 +721,152 @@ gloss:RelatedConceptShape a sh:NodeShape ;
|
|
|
699
721
|
sh:maxCount 1 ;
|
|
700
722
|
] .
|
|
701
723
|
|
|
724
|
+
# ══════════════════════════════════════════════════════════════════════════
|
|
725
|
+
# Partitive Hyperedge shapes
|
|
726
|
+
# ══════════════════════════════════════════════════════════════════════════
|
|
727
|
+
|
|
728
|
+
gloss:PartitiveHyperedgeShape a sh:NodeShape ;
|
|
729
|
+
sh:targetClass gloss:PartitiveHyperedge ;
|
|
730
|
+
sh:property [
|
|
731
|
+
sh:path gloss:comprehensive ;
|
|
732
|
+
sh:class gloss:ConceptRef ;
|
|
733
|
+
sh:minCount 1 ;
|
|
734
|
+
sh:maxCount 1 ;
|
|
735
|
+
] ;
|
|
736
|
+
sh:property [
|
|
737
|
+
sh:path gloss:hasPart ;
|
|
738
|
+
sh:class gloss:ConceptRef ;
|
|
739
|
+
sh:minCount 1 ;
|
|
740
|
+
] ;
|
|
741
|
+
sh:property [
|
|
742
|
+
sh:path gloss:enumeration ;
|
|
743
|
+
sh:class skos:Concept ;
|
|
744
|
+
sh:valuesFrom <https://www.glossarist.org/ontologies/partitiveEnumeration> ;
|
|
745
|
+
sh:maxCount 1 ;
|
|
746
|
+
] ;
|
|
747
|
+
sh:property [
|
|
748
|
+
sh:path gloss:hasPluralityMarker ;
|
|
749
|
+
sh:class skos:Concept ;
|
|
750
|
+
sh:valuesFrom <https://www.glossarist.org/ontologies/pluralityMarker> ;
|
|
751
|
+
] ;
|
|
752
|
+
sh:property [
|
|
753
|
+
sh:path gloss:hyperedgeContent ;
|
|
754
|
+
sh:datatype xsd:string ;
|
|
755
|
+
sh:maxCount 1 ;
|
|
756
|
+
] .
|
|
757
|
+
|
|
758
|
+
# Ownership invariant — soft (warning severity). A PartitiveHyperedge
|
|
759
|
+
# is *conventionally* carried by its comprehensive's own concept entry:
|
|
760
|
+
# <concept/X> gloss:hasPartitiveHyperedge [
|
|
761
|
+
# gloss:comprehensive [ a gloss:ConceptRef ; gloss:ref-id "X" ]
|
|
762
|
+
# ] .
|
|
763
|
+
#
|
|
764
|
+
# Datasets in the wild may legitimately cross-reference (a concept
|
|
765
|
+
# carries a hyperedge whose comprehensive points elsewhere — e.g. for
|
|
766
|
+
# indexing). We do not forbid this; we warn so reviewers can confirm
|
|
767
|
+
# the cross-reference is intentional.
|
|
768
|
+
#
|
|
769
|
+
# The SPARQL constraint matches when the carrying concept's URI-ending
|
|
770
|
+
# identifier differs from the comprehensive ConceptRef's `gloss:id`
|
|
771
|
+
# literal.
|
|
772
|
+
gloss:PartitiveHyperedgeOwnershipShape a sh:NodeShape ;
|
|
773
|
+
sh:targetClass gloss:PartitiveHyperedge ;
|
|
774
|
+
sh:severity sh:Warning ;
|
|
775
|
+
sh:message "partitive_hyperedge is carried by a concept that is not its comprehensive — confirm the cross-reference is intentional."@en ;
|
|
776
|
+
sh:sparql [
|
|
777
|
+
a sh:SPARQLConstraint ;
|
|
778
|
+
sh:prefixes [
|
|
779
|
+
sh:declare [
|
|
780
|
+
sh:prefix "gloss" ;
|
|
781
|
+
sh:namespace "https://www.glossarist.org/ontologies/"^^xsd:anyURI ;
|
|
782
|
+
] ;
|
|
783
|
+
sh:declare [
|
|
784
|
+
sh:prefix "xsd" ;
|
|
785
|
+
sh:namespace "http://www.w3.org/2001/XMLSchema#"^^xsd:anyURI ;
|
|
786
|
+
] ;
|
|
787
|
+
] ;
|
|
788
|
+
sh:select """
|
|
789
|
+
SELECT $this ?carryingConcept ?compRef
|
|
790
|
+
WHERE {
|
|
791
|
+
?carryingConcept gloss:hasPartitiveHyperedge $this .
|
|
792
|
+
$this gloss:comprehensive ?compRef .
|
|
793
|
+
# Two encoding shapes exist:
|
|
794
|
+
# (a) ?compRef is a gloss:ConceptRef node with gloss:conceptRefId
|
|
795
|
+
# (b) ?compRef is a direct URI ending in /<concept_id>
|
|
796
|
+
# We bind ?compKey to whichever form is present, then compare
|
|
797
|
+
# against the URI suffix of the carrying concept.
|
|
798
|
+
OPTIONAL { ?compRef gloss:conceptRefId ?literalId . }
|
|
799
|
+
BIND(COALESCE(?literalId, STRAFTER(STR(?compRef), CONCAT(STR(?carryingConcept), "/"))) AS ?compKey)
|
|
800
|
+
FILTER (BOUND(?compKey) && !STRENDS(STR(?carryingConcept), CONCAT("/", ?compKey)))
|
|
801
|
+
}
|
|
802
|
+
""" ;
|
|
803
|
+
] .
|
|
804
|
+
|
|
805
|
+
# ── Enum scheme shapes ───────────────────────────────────────────────────
|
|
806
|
+
# PartitiveEnumeration and PluralityMarker are modelled as owl:Class
|
|
807
|
+
# but functionally are SKOS ConceptSchemes. The shape asserts the
|
|
808
|
+
# scheme contract: every instance must be a skos:ConceptScheme.
|
|
809
|
+
|
|
810
|
+
gloss:PartitiveEnumerationShape a sh:NodeShape ;
|
|
811
|
+
sh:targetClass gloss:PartitiveEnumeration ;
|
|
812
|
+
sh:class skos:ConceptScheme .
|
|
813
|
+
|
|
814
|
+
gloss:PluralityMarkerShape a sh:NodeShape ;
|
|
815
|
+
sh:targetClass gloss:PluralityMarker ;
|
|
816
|
+
sh:class skos:ConceptScheme .
|
|
817
|
+
|
|
818
|
+
# ── Non-verbal entity hierarchy ──────────────────────────────────────────
|
|
819
|
+
# Abstract base + shared dataset-wide entity. Concrete shapes
|
|
820
|
+
# (Figure, Table, Formula) live elsewhere in this file.
|
|
821
|
+
|
|
822
|
+
gloss:NonVerbalEntityShape a sh:NodeShape ;
|
|
823
|
+
sh:targetClass gloss:NonVerbalEntity ;
|
|
824
|
+
sh:property [
|
|
825
|
+
sh:path gloss:caption ;
|
|
826
|
+
sh:datatype xsd:string ;
|
|
827
|
+
sh:maxCount 1 ;
|
|
828
|
+
] ;
|
|
829
|
+
sh:property [
|
|
830
|
+
sh:path gloss:altText ;
|
|
831
|
+
sh:datatype xsd:string ;
|
|
832
|
+
sh:maxCount 1 ;
|
|
833
|
+
] ;
|
|
834
|
+
sh:property [
|
|
835
|
+
sh:path gloss:description ;
|
|
836
|
+
sh:datatype xsd:string ;
|
|
837
|
+
] ;
|
|
838
|
+
sh:property [
|
|
839
|
+
sh:path gloss:hasSource ;
|
|
840
|
+
sh:class gloss:ConceptSource ;
|
|
841
|
+
] .
|
|
842
|
+
|
|
843
|
+
gloss:SharedNonVerbalEntityShape a sh:NodeShape ;
|
|
844
|
+
sh:targetClass gloss:SharedNonVerbalEntity ;
|
|
845
|
+
sh:class gloss:NonVerbalEntity ;
|
|
846
|
+
sh:property [
|
|
847
|
+
sh:path gloss:role ;
|
|
848
|
+
sh:datatype xsd:string ;
|
|
849
|
+
sh:maxCount 1 ;
|
|
850
|
+
] .
|
|
851
|
+
|
|
852
|
+
gloss:FigureImageShape a sh:NodeShape ;
|
|
853
|
+
sh:targetClass gloss:FigureImage ;
|
|
854
|
+
sh:property [
|
|
855
|
+
sh:path gloss:src ;
|
|
856
|
+
sh:datatype xsd:string ;
|
|
857
|
+
sh:minCount 1 ;
|
|
858
|
+
sh:maxCount 1 ;
|
|
859
|
+
] ;
|
|
860
|
+
sh:property [
|
|
861
|
+
sh:path gloss:format ;
|
|
862
|
+
sh:datatype xsd:string ;
|
|
863
|
+
sh:maxCount 1 ;
|
|
864
|
+
] ;
|
|
865
|
+
sh:property [
|
|
866
|
+
sh:path gloss:hasSubfigure ;
|
|
867
|
+
sh:class gloss:FigureImage ;
|
|
868
|
+
] .
|
|
869
|
+
|
|
702
870
|
gloss:GrammarInfoShape a sh:NodeShape ;
|
|
703
871
|
sh:targetClass gloss:GrammarInfo ;
|
|
704
872
|
sh:property [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glossarist/concept-browser",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.136",
|
|
4
4
|
"description": "Vue SPA for browsing Glossarist terminology datasets with cross-reference resolution, graph visualization, and multi-language support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"astro": "^7.0.7",
|
|
44
44
|
"d3": "^7.9.0",
|
|
45
45
|
"favicons": "^7.2.0",
|
|
46
|
-
"glossarist": "^0.4.
|
|
46
|
+
"glossarist": "^0.4.58",
|
|
47
47
|
"js-yaml": "^4.1.0",
|
|
48
48
|
"jszip": "^3.10.1",
|
|
49
49
|
"n3": "^1.26.0",
|
|
@@ -55,24 +55,64 @@ describe('doctor — runDoctor on synthetic projects', () => {
|
|
|
55
55
|
|
|
56
56
|
it('fails on missing datasets.yml', async () => {
|
|
57
57
|
const { results } = await runDoctor(root);
|
|
58
|
-
const ymlCheck = results.find((r) => r.id === 'datasets-
|
|
58
|
+
const ymlCheck = results.find((r) => r.id === 'datasets-registered');
|
|
59
59
|
expect(ymlCheck.status).toBe('warn');
|
|
60
60
|
});
|
|
61
61
|
|
|
62
62
|
it('fails on malformed datasets.yml', async () => {
|
|
63
63
|
writeFileSync(join(root, 'datasets.yml'), 'datasets: [this is broken');
|
|
64
64
|
const { results, exitCode } = await runDoctor(root);
|
|
65
|
-
const ymlCheck = results.find((r) => r.id === 'datasets-
|
|
65
|
+
const ymlCheck = results.find((r) => r.id === 'datasets-registered');
|
|
66
66
|
expect(ymlCheck.status).toBe('fail');
|
|
67
67
|
expect(exitCode).toBe(1);
|
|
68
68
|
});
|
|
69
69
|
|
|
70
|
-
it('passes datasets-
|
|
70
|
+
it('passes datasets-registered when the file parses and lists entries', async () => {
|
|
71
71
|
writeDatasetsYml(root, ['foo', 'bar']);
|
|
72
72
|
const { results } = await runDoctor(root);
|
|
73
|
-
const ymlCheck = results.find((r) => r.id === 'datasets-
|
|
73
|
+
const ymlCheck = results.find((r) => r.id === 'datasets-registered');
|
|
74
74
|
expect(ymlCheck.status).toBe('pass');
|
|
75
75
|
expect(ymlCheck.label).toContain('2 dataset(s)');
|
|
76
|
+
expect(ymlCheck.label).toContain('datasets.yml');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('registers datasets from site-config.yml when datasets.yml is absent', async () => {
|
|
80
|
+
writeFileSync(join(root, 'site-config.yml'), 'datasets:\n - id: viml-2022\n local_path: datasets/viml-2022\n');
|
|
81
|
+
mkdirSync(join(root, 'datasets', 'viml-2022'), { recursive: true });
|
|
82
|
+
stubGenerated(root, ['viml-2022']);
|
|
83
|
+
const { results, exitCode } = await runDoctor(root);
|
|
84
|
+
const reg = results.find((r) => r.id === 'datasets-registered');
|
|
85
|
+
expect(reg.status).toBe('pass');
|
|
86
|
+
expect(reg.label).toContain('site-config.yml');
|
|
87
|
+
const fetched = results.find((r) => r.id === 'datasets-fetched');
|
|
88
|
+
expect(fetched.status).toBe('pass');
|
|
89
|
+
expect(exitCode).toBe(0);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('fails datasets-fetched when a site-config local_path is missing', async () => {
|
|
93
|
+
writeFileSync(join(root, 'site-config.yml'), 'datasets:\n - id: viml-2022\n local_path: datasets/viml-2022\n');
|
|
94
|
+
const { results, exitCode } = await runDoctor(root);
|
|
95
|
+
const fetched = results.find((r) => r.id === 'datasets-fetched');
|
|
96
|
+
expect(fetched.status).toBe('fail');
|
|
97
|
+
expect(fetched.detail).toContain('viml-2022');
|
|
98
|
+
expect(exitCode).toBe(1);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('reports generated manifests for site-config datasets', async () => {
|
|
102
|
+
writeFileSync(join(root, 'site-config.yml'), 'datasets:\n - id: viml-2022\n local_path: datasets/viml-2022\n');
|
|
103
|
+
mkdirSync(join(root, 'datasets', 'viml-2022'), { recursive: true });
|
|
104
|
+
stubGenerated(root, ['viml-2022']);
|
|
105
|
+
const { results } = await runDoctor(root);
|
|
106
|
+
const gen = results.find((r) => r.id === 'datasets-generated');
|
|
107
|
+
expect(gen.status).toBe('pass');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('fails on malformed site-config.yml', async () => {
|
|
111
|
+
writeFileSync(join(root, 'site-config.yml'), 'datasets: [broken');
|
|
112
|
+
const { results, exitCode } = await runDoctor(root);
|
|
113
|
+
const reg = results.find((r) => r.id === 'datasets-registered');
|
|
114
|
+
expect(reg.status).toBe('fail');
|
|
115
|
+
expect(exitCode).toBe(1);
|
|
76
116
|
});
|
|
77
117
|
|
|
78
118
|
it('fails when registered datasets are not fetched', async () => {
|
|
@@ -125,7 +165,7 @@ describe('doctor — runDoctor on synthetic projects', () => {
|
|
|
125
165
|
it('does not crash when datasets.yml lists zero datasets', async () => {
|
|
126
166
|
writeFileSync(join(root, 'datasets.yml'), 'datasets: []\n');
|
|
127
167
|
const { results } = await runDoctor(root);
|
|
128
|
-
const ymlCheck = results.find((r) => r.id === 'datasets-
|
|
168
|
+
const ymlCheck = results.find((r) => r.id === 'datasets-registered');
|
|
129
169
|
expect(ymlCheck.status).toBe('warn');
|
|
130
170
|
});
|
|
131
171
|
});
|
package/scripts/doctor.js
CHANGED
|
@@ -35,8 +35,8 @@ function nodeVersionCheck() {
|
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
37
|
function packageDepsCheck(ctx) {
|
|
38
|
-
const
|
|
39
|
-
if (!existsSync(nm)) {
|
|
38
|
+
const roots = [resolve(ctx.cwd, "node_modules"), resolve(ctx.pkgRoot, "node_modules")];
|
|
39
|
+
if (!roots.some((nm) => existsSync(nm))) {
|
|
40
40
|
return {
|
|
41
41
|
id: "package-deps",
|
|
42
42
|
label: "Dependencies installed",
|
|
@@ -46,7 +46,7 @@ function packageDepsCheck(ctx) {
|
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
48
|
const required = ["vue", "vue-router", "pinia", "vite", "n3", "glossarist", "@rdfjs/dataset"];
|
|
49
|
-
const missing = required.filter((p) => !existsSync(join(nm, p)));
|
|
49
|
+
const missing = required.filter((p) => !roots.some((nm) => existsSync(join(nm, p))));
|
|
50
50
|
if (missing.length) {
|
|
51
51
|
return {
|
|
52
52
|
id: "package-deps",
|
|
@@ -58,63 +58,86 @@ function packageDepsCheck(ctx) {
|
|
|
58
58
|
}
|
|
59
59
|
return { id: "package-deps", label: "Dependencies installed", status: "pass" };
|
|
60
60
|
}
|
|
61
|
-
function
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
61
|
+
function resolveDatasetRegistry(cwd, fileError = void 0) {
|
|
62
|
+
if (fileError) return { registered: [], source: "datasets.yml", error: fileError };
|
|
63
|
+
const ymlPath = resolve(cwd, "datasets.yml");
|
|
64
|
+
if (existsSync(ymlPath)) {
|
|
65
|
+
const list = normalizeRegistry(yaml.load(readFileSync(ymlPath, "utf8")));
|
|
66
|
+
return { registered: list, source: "datasets.yml" };
|
|
67
|
+
}
|
|
68
|
+
const sitePath = resolve(cwd, "site-config.yml");
|
|
69
|
+
if (existsSync(sitePath)) {
|
|
70
|
+
try {
|
|
71
|
+
const list = normalizeRegistry(yaml.load(readFileSync(sitePath, "utf8")));
|
|
72
|
+
return { registered: list, source: "site-config.yml" };
|
|
73
|
+
} catch (err) {
|
|
74
|
+
return { registered: [], source: "site-config.yml", error: err.message };
|
|
75
|
+
}
|
|
71
76
|
}
|
|
72
|
-
|
|
77
|
+
return { registered: [], source: null };
|
|
78
|
+
}
|
|
79
|
+
function normalizeRegistry(doc) {
|
|
80
|
+
const datasets = doc?.datasets;
|
|
81
|
+
if (!Array.isArray(datasets)) return [];
|
|
82
|
+
return datasets.filter((d) => d && typeof d === "object" && d.id).map((d) => ({ id: String(d.id), localPath: d.local_path ?? d.localPath }));
|
|
83
|
+
}
|
|
84
|
+
function datasetsRegisteredCheck(ctx) {
|
|
85
|
+
if (ctx.registry.error) {
|
|
73
86
|
return {
|
|
74
|
-
id: "datasets-
|
|
75
|
-
label:
|
|
87
|
+
id: "datasets-registered",
|
|
88
|
+
label: `${ctx.registry.source} parses`,
|
|
76
89
|
status: "fail",
|
|
77
|
-
detail: ctx.
|
|
90
|
+
detail: ctx.registry.error,
|
|
78
91
|
hint: "fix the YAML syntax error"
|
|
79
92
|
};
|
|
80
93
|
}
|
|
81
|
-
|
|
82
|
-
if (!Array.isArray(datasets) || datasets.length === 0) {
|
|
94
|
+
if (!ctx.registry.source) {
|
|
83
95
|
return {
|
|
84
|
-
id: "datasets-
|
|
85
|
-
label: "
|
|
96
|
+
id: "datasets-registered",
|
|
97
|
+
label: "Dataset registry present",
|
|
86
98
|
status: "warn",
|
|
87
|
-
detail: "
|
|
99
|
+
detail: "neither datasets.yml nor site-config.yml found at project root",
|
|
100
|
+
hint: "datasets.yml / site-config.yml register every dataset the browser serves"
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if (ctx.registry.registered.length === 0) {
|
|
104
|
+
return {
|
|
105
|
+
id: "datasets-registered",
|
|
106
|
+
label: "Dataset registry lists datasets",
|
|
107
|
+
status: "warn",
|
|
108
|
+
detail: `\`datasets\` key is empty or missing in ${ctx.registry.source}`,
|
|
88
109
|
hint: "add at least one dataset entry; see docs/adding-a-dataset.md"
|
|
89
110
|
};
|
|
90
111
|
}
|
|
91
112
|
return {
|
|
92
|
-
id: "datasets-
|
|
93
|
-
label:
|
|
113
|
+
id: "datasets-registered",
|
|
114
|
+
label: `${ctx.registry.source} lists ${ctx.registry.registered.length} dataset(s)`,
|
|
94
115
|
status: "pass"
|
|
95
116
|
};
|
|
96
117
|
}
|
|
97
118
|
function datasetsFetchedCheck(ctx) {
|
|
98
|
-
const datasets = ctx.
|
|
99
|
-
if (!datasets.length) {
|
|
119
|
+
const datasets = ctx.registry.registered;
|
|
120
|
+
if (!ctx.registry.source || !datasets.length) {
|
|
100
121
|
return { id: "datasets-fetched", label: "Source datasets fetched", status: "warn", detail: "no datasets registered" };
|
|
101
122
|
}
|
|
102
|
-
const
|
|
103
|
-
|
|
123
|
+
const missing = datasets.filter((d) => {
|
|
124
|
+
if (d.localPath) return !existsSync(resolve(ctx.cwd, d.localPath));
|
|
125
|
+
return !existsSync(join(resolve(ctx.cwd, ".datasets"), d.id, "concepts"));
|
|
126
|
+
}).map((d) => d.id);
|
|
104
127
|
if (missing.length) {
|
|
105
128
|
return {
|
|
106
129
|
id: "datasets-fetched",
|
|
107
130
|
label: "Source datasets fetched",
|
|
108
131
|
status: "fail",
|
|
109
|
-
detail:
|
|
110
|
-
hint: "run `npm run fetch-datasets`"
|
|
132
|
+
detail: `dataset sources missing: ${missing.join(", ")}`,
|
|
133
|
+
hint: "run `npm run fetch-datasets` (or check local_path entries in site-config.yml)"
|
|
111
134
|
};
|
|
112
135
|
}
|
|
113
136
|
return { id: "datasets-fetched", label: "Source datasets fetched", status: "pass" };
|
|
114
137
|
}
|
|
115
138
|
function datasetsGeneratedCheck(ctx) {
|
|
116
|
-
const datasets = ctx.
|
|
117
|
-
if (!datasets.length) {
|
|
139
|
+
const datasets = ctx.registry.registered;
|
|
140
|
+
if (!ctx.registry.source || !datasets.length) {
|
|
118
141
|
return { id: "datasets-generated", label: "Generated data present", status: "warn", detail: "no datasets registered" };
|
|
119
142
|
}
|
|
120
143
|
const publicData = resolve(ctx.cwd, "public", "data");
|
|
@@ -216,7 +239,7 @@ function jsonldContextCheck(ctx) {
|
|
|
216
239
|
var CHECKS = [
|
|
217
240
|
safe("node-version", nodeVersionCheck),
|
|
218
241
|
safe("package-deps", packageDepsCheck),
|
|
219
|
-
safe("datasets-
|
|
242
|
+
safe("datasets-registered", datasetsRegisteredCheck),
|
|
220
243
|
safe("datasets-fetched", datasetsFetchedCheck),
|
|
221
244
|
safe("datasets-generated", datasetsGeneratedCheck),
|
|
222
245
|
safe("public-datasets-json", publicDatasetsJsonCheck),
|
|
@@ -234,7 +257,13 @@ async function runDoctor(cwd = process.cwd()) {
|
|
|
234
257
|
datasetsYmlError = err.message;
|
|
235
258
|
}
|
|
236
259
|
}
|
|
237
|
-
const ctx = {
|
|
260
|
+
const ctx = {
|
|
261
|
+
cwd,
|
|
262
|
+
pkgRoot: PKG_ROOT,
|
|
263
|
+
datasetsYml,
|
|
264
|
+
datasetsYmlError,
|
|
265
|
+
registry: resolveDatasetRegistry(cwd, datasetsYmlError)
|
|
266
|
+
};
|
|
238
267
|
const results = [];
|
|
239
268
|
for (const check of CHECKS) {
|
|
240
269
|
results.push(await check(ctx));
|
|
@@ -274,5 +303,6 @@ export {
|
|
|
274
303
|
NODE_MIN_MAJOR,
|
|
275
304
|
formatResults,
|
|
276
305
|
main,
|
|
306
|
+
resolveDatasetRegistry,
|
|
277
307
|
runDoctor
|
|
278
308
|
};
|