glossarist 0.3.7 → 0.4.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,19 @@
1
+ const FALLBACK_LANG = 'eng';
2
+
3
+ export function fetchLocalizedString(hash, lang, fallback = FALLBACK_LANG) {
4
+ if (hash == null || typeof hash !== 'object') return null;
5
+ const direct = hash[lang] ?? hash[String(lang)];
6
+ if (direct != null) return direct;
7
+ if (fallback != null && fallback !== lang) {
8
+ return hash[fallback] ?? hash[String(fallback)] ?? null;
9
+ }
10
+ return null;
11
+ }
12
+
13
+ export function localizedStringIsEmpty(hash) {
14
+ return hash == null || (typeof hash === 'object' && Object.keys(hash).length === 0);
15
+ }
16
+
17
+ export function localizedStringIsPresent(hash) {
18
+ return !localizedStringIsEmpty(hash);
19
+ }
@@ -1,23 +1,25 @@
1
- import { GlossaristModel } from './base.js';
2
- import { ConceptSource } from './concept-source.js';
1
+ import { NonVerbalEntity } from './non-verbal-entity.js';
2
+ import { FigureImage } from './figure.js';
3
3
 
4
- export class NonVerbRep extends GlossaristModel {
4
+ export const NON_VERBAL_TYPES = Object.freeze(['image', 'table', 'formula']);
5
+
6
+ export class NonVerbRep extends NonVerbalEntity {
5
7
  constructor(data = {}) {
6
- super();
8
+ super(data);
7
9
  this.type = data.type ?? null;
8
- this.ref = data.ref ?? null;
9
- this.text = data.text ?? null;
10
- this.sources = (data.sources ?? []).map(
11
- s => s instanceof ConceptSource ? s : new ConceptSource(s)
12
- );
10
+ this._rawImages = data.images ?? [];
11
+ this._images = null;
12
+ }
13
+
14
+ get images() {
15
+ return this._lazy('_images', '_rawImages',
16
+ i => i instanceof FigureImage ? i : new FigureImage(i));
13
17
  }
14
18
 
15
19
  toJSON() {
16
- const obj = {};
20
+ const obj = super.toJSON();
17
21
  if (this.type != null) obj.type = this.type;
18
- if (this.ref != null) obj.ref = this.ref;
19
- if (this.text != null) obj.text = this.text;
20
- if (this.sources.length > 0) obj.sources = this.sources.map(s => s.toJSON());
22
+ this._serialize(obj, 'images', '_images', '_rawImages');
21
23
  return obj;
22
24
  }
23
25
 
@@ -0,0 +1,39 @@
1
+ import { RegistrableModel } from './registrable.js';
2
+ import { ConceptSource } from './concept-source.js';
3
+
4
+ export class NonVerbalEntity extends RegistrableModel {
5
+ constructor(data = {}) {
6
+ super();
7
+ this.caption = data.caption ?? null;
8
+ this.description = data.description ?? null;
9
+ this.alt = data.alt ?? null;
10
+ this._rawSources = data.sources ?? [];
11
+ this._sources = null;
12
+ }
13
+
14
+ get sources() {
15
+ return this._lazy('_sources', '_rawSources',
16
+ s => s instanceof ConceptSource ? s : new ConceptSource(s));
17
+ }
18
+
19
+ findById(_targetId) {
20
+ return null;
21
+ }
22
+
23
+ allIds() {
24
+ return [];
25
+ }
26
+
27
+ toJSON() {
28
+ const obj = {};
29
+ if (this.caption != null) obj.caption = this.caption;
30
+ if (this.description != null) obj.description = this.description;
31
+ if (this.alt != null) obj.alt = this.alt;
32
+ this._serialize(obj, 'sources', '_sources', '_rawSources');
33
+ return obj;
34
+ }
35
+
36
+ static fromJSON(data) {
37
+ return NonVerbalEntity.fromData(data);
38
+ }
39
+ }
@@ -0,0 +1,35 @@
1
+ import { RegistrableModel } from './registrable.js';
2
+
3
+ export class NonVerbalReference extends RegistrableModel {
4
+ constructor(data = {}) {
5
+ super();
6
+ this.entityId = data.entityId ?? data.entity_id ?? data.ref ?? data.id ?? null;
7
+ this.display = data.display ?? null;
8
+ }
9
+
10
+ get dedupKey() {
11
+ return [this.constructor.name, this.entityId];
12
+ }
13
+
14
+ toJSON() {
15
+ if (this.display != null) {
16
+ return { ref: this.entityId, display: this.display };
17
+ }
18
+ return this.entityId;
19
+ }
20
+
21
+ static fromJSON(data) {
22
+ if (data instanceof NonVerbalReference) return data;
23
+ if (typeof data === 'string') {
24
+ return new NonVerbalReference({ entityId: data });
25
+ }
26
+ const entityId = data.entityId ?? data.entity_id ?? data.ref ?? data.id ?? null;
27
+ const display = data.display ?? null;
28
+ const type = data.type;
29
+ if (type && this._registry().has(type)) {
30
+ const Cls = this._registry().get(type);
31
+ return new Cls({ entityId, display });
32
+ }
33
+ return new NonVerbalReference({ entityId, display });
34
+ }
35
+ }
@@ -0,0 +1,16 @@
1
+ import { NonVerbalReference } from './non-verbal-reference.js';
2
+
3
+ export class FigureReference extends NonVerbalReference {
4
+ static fromJSON(data) { return NonVerbalReference.fromJSON(data); }
5
+ }
6
+ NonVerbalReference.register('figure', FigureReference);
7
+
8
+ export class TableReference extends NonVerbalReference {
9
+ static fromJSON(data) { return NonVerbalReference.fromJSON(data); }
10
+ }
11
+ NonVerbalReference.register('table', TableReference);
12
+
13
+ export class FormulaReference extends NonVerbalReference {
14
+ static fromJSON(data) { return NonVerbalReference.fromJSON(data); }
15
+ }
16
+ NonVerbalReference.register('formula', FormulaReference);
@@ -0,0 +1,25 @@
1
+ import { GlossaristModel } from './base.js';
2
+
3
+ const _registries = new WeakMap();
4
+
5
+ export class RegistrableModel extends GlossaristModel {
6
+ static _registry() {
7
+ let map = _registries.get(this);
8
+ if (!map) {
9
+ map = new Map();
10
+ _registries.set(this, map);
11
+ }
12
+ return map;
13
+ }
14
+
15
+ static register(type, cls) {
16
+ this._registry().set(type, cls);
17
+ }
18
+
19
+ static fromData(data) {
20
+ if (data instanceof this) return data;
21
+ const type = data?.type;
22
+ const Cls = type ? this._registry().get(type) ?? this : this;
23
+ return new Cls(data);
24
+ }
25
+ }
@@ -0,0 +1,28 @@
1
+ import { NonVerbalEntity } from './non-verbal-entity.js';
2
+
3
+ export class SharedNonVerbalEntity extends NonVerbalEntity {
4
+ constructor(data = {}) {
5
+ super(data);
6
+ this.id = data.id ?? null;
7
+ this.identifier = data.identifier ?? null;
8
+ }
9
+
10
+ findById(targetId) {
11
+ return this.id === targetId ? this : null;
12
+ }
13
+
14
+ allIds() {
15
+ return this.id != null ? [this.id] : [];
16
+ }
17
+
18
+ toJSON() {
19
+ const obj = super.toJSON();
20
+ if (this.id != null) obj.id = this.id;
21
+ if (this.identifier != null) obj.identifier = this.identifier;
22
+ return obj;
23
+ }
24
+
25
+ static fromJSON(data) {
26
+ return SharedNonVerbalEntity.fromData(data);
27
+ }
28
+ }
@@ -0,0 +1,21 @@
1
+ import { SharedNonVerbalEntity } from './shared-non-verbal-entity.js';
2
+ import { NonVerbalEntity } from './non-verbal-entity.js';
3
+
4
+ export class Table extends SharedNonVerbalEntity {
5
+ constructor(data = {}) {
6
+ super(data);
7
+ this.content = data.content ?? null;
8
+ this.format = data.format ?? null;
9
+ }
10
+
11
+ toJSON() {
12
+ const obj = super.toJSON();
13
+ if (this.content != null) obj.content = this.content;
14
+ if (this.format != null) obj.format = this.format;
15
+ return obj;
16
+ }
17
+
18
+ static fromJSON(data) { return new Table(data); }
19
+ }
20
+
21
+ NonVerbalEntity.register('table', Table);
@@ -27,6 +27,12 @@
27
27
 
28
28
  const NUMERIC_RE = /^\d+(?:[.-]\d+)+$/;
29
29
 
30
+ const NVR_PREFIXES = Object.freeze([
31
+ { prefix: 'fig:', kind: 'fig-ref' },
32
+ { prefix: 'table:', kind: 'table-ref' },
33
+ { prefix: 'formula:', kind: 'formula-ref' },
34
+ ]);
35
+
30
36
  /**
31
37
  * Parse the body of a {{...}} mention (without the braces).
32
38
  *
@@ -63,7 +69,17 @@ export function parseMention(raw) {
63
69
  };
64
70
  }
65
71
 
66
- // 3. Comma-separated form: {{id, render}}.
72
+ // 3. NVR prefixes (fig:/table:/formula:) config-driven dispatch.
73
+ for (const { prefix, kind } of NVR_PREFIXES) {
74
+ const escPrefix = prefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
75
+ const match = body.match(new RegExp(`^${escPrefix}([^,}]+)(?:,(.*))?$`));
76
+ if (match) {
77
+ const label = match[2] !== undefined ? unquoteLabel(match[2].trim()) : null;
78
+ return { kind, key: match[1].trim(), label, raw: body };
79
+ }
80
+ }
81
+
82
+ // 4. Comma-separated form: {{id, render}}.
67
83
  // ID always comes first, render text always comes last.
68
84
  const commaIdx = body.indexOf(',');
69
85
  if (commaIdx !== -1) {
@@ -76,12 +92,12 @@ export function parseMention(raw) {
76
92
  return { kind: 'designation', id, label, raw: body };
77
93
  }
78
94
 
79
- // 4. Bare numeric id.
95
+ // 5. Bare numeric id.
80
96
  if (NUMERIC_RE.test(body)) {
81
97
  return { kind: 'numeric', id: body, label: null, raw: body };
82
98
  }
83
99
 
84
- // 5. Anything else is unresolved at the parse layer.
100
+ // 6. Anything else is unresolved at the parse layer.
85
101
  return { kind: 'unresolved', raw: body };
86
102
  }
87
103