@eten-tech-foundation/scripture-utilities 0.1.5 → 0.1.6

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,238 @@
1
+ /** Serializable USJ locations relative to a specific USJ document (chapter or book) */
2
+
3
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars -- Used in TSDoc @link references
4
+ import type { MarkerContent, MarkerObject, Usj } from "./usj.model.js";
5
+
6
+ /**
7
+ * A JSONPath query to a {@link MarkerContent}, {@link Usj}, or property within a USJ document and
8
+ * additional information that point to a specific location in that USJ document.
9
+ *
10
+ * This type does not include a verse reference because the JSONPath is relative to a specific USJ
11
+ * document; that USJ document may have a book, a chapter, or something else in it. Use
12
+ * `UsjLocation` to specify which USJ document this location is relative to, making the
13
+ * location an absolute verse reference location. The closest equivalent concept in USFM to this
14
+ * relative document location is a string character index in a USFM document; such an index is
15
+ * relative to a specific USFM document rather than indicating an absolute position in a Scripture
16
+ * text.
17
+ *
18
+ * This type intends to represent USFM positions (`UsfmVerseLocation`) in USJ space. However,
19
+ * there are some USFM positions that are not currently representable with these types:
20
+ *
21
+ * - The second slash in `optbreak`'s USFM representation `//` (literally not representable)
22
+ * - Nested marker prefix on opening markers like `+` for character markers (literally not
23
+ * representable)
24
+ * - The bar `|` that indicates the start of closing marker attributes (no official representation)
25
+ * - The equals sign for closing marker attributes (no official representation)
26
+ * - The quotes around closing marker attribute values (no official representation)
27
+ * - The space between closing marker attributes (no official representation)
28
+ *
29
+ * Also note that the following types do not specify a concrete location that is actually in the USJ
30
+ * document but represent a USFM location relative to the most similar thing in USJ that there is:
31
+ *
32
+ * - {@link UsjClosingMarkerLocation} - there are no distinct closing objects in JSON; there is a
33
+ * common syntax for closing every object, but it is only one character and is on every single
34
+ * object as opposed to USFM closing markers which are multiple characters long and are only
35
+ * sometimes present.
36
+ * - {@link UsjAttributeKeyLocation} - when the attribute whose key is being pointed to is an
37
+ * attribute marker in USFM, the `keyOffset` does not apply to the USJ attribute name (e.g.
38
+ * `altnumber`) but to the USFM attribute marker name (e.g. `ca`).
39
+ * - {@link UsjAttributeMarkerLocation} - attribute markers are just properties in JSON; they do not
40
+ * have their own object such that they would have an opening that can be pointed to in the JSON
41
+ * like they have their own opening in USFM.
42
+ * - {@link UsjClosingAttributeMarkerLocation} - attribute markers are just properties in JSON, plus
43
+ * they are in the same situation as {@link UsjClosingMarkerLocation} as detailed above.
44
+ *
45
+ * To see many examples of the same point represented by both USFM and USJ locations, go to
46
+ * https://github.com/paranext/paranext-core/tree/main/lib/platform-bible-utils/src/scripture/usj-reader-writer-test-data/testUSFM-2SA-1-locations.ts
47
+ *
48
+ * @public
49
+ */
50
+ export type UsjDocumentLocation =
51
+ | UsjMarkerLocation
52
+ | UsjClosingMarkerLocation
53
+ | UsjTextContentLocation
54
+ | UsjPropertyValueLocation
55
+ | UsjAttributeKeyLocation
56
+ | UsjAttributeMarkerLocation
57
+ | UsjClosingAttributeMarkerLocation;
58
+
59
+ /**
60
+ * A JSONPath query to a {@link MarkerObject} or {@link Usj} node. Indicates the very beginning of
61
+ * that marker (at the backslash in USFM).
62
+ *
63
+ * @public
64
+ */
65
+ export interface UsjMarkerLocation {
66
+ /** JSON path to the marker object the location is pointing to. */
67
+ jsonPath: ContentJsonPath;
68
+ }
69
+
70
+ /**
71
+ * A JSONPath query to a specific point in the closing marker representation of a
72
+ * {@link MarkerObject} or {@link Usj} node.
73
+ *
74
+ * @public
75
+ */
76
+ export interface UsjClosingMarkerLocation {
77
+ /**
78
+ * JSON path to the marker object whose closing marker the location is pointing to. The offset
79
+ * applies to the closing marker representation of that marker (for example, `\nd*` in USFM).
80
+ */
81
+ jsonPath: ContentJsonPath;
82
+ /**
83
+ * The character index in the closing marker representation where this location is pointing. The
84
+ * location is at this offset within the closing marker representation.
85
+ */
86
+ closingMarkerOffset: number;
87
+ }
88
+
89
+ /**
90
+ * A JSONPath query to a specific point in a text content string in a {@link MarkerObject.content}
91
+ * array.
92
+ *
93
+ * @public
94
+ */
95
+ export interface UsjTextContentLocation {
96
+ /**
97
+ * JSON path to the text content string the location is pointing to. The offset applies to this
98
+ * text string.
99
+ */
100
+ jsonPath: ContentJsonPath;
101
+ /**
102
+ * The character index in the text content string where this location is pointing. The location is
103
+ * at this offset within the text content string.
104
+ */
105
+ offset: number;
106
+ }
107
+
108
+ /**
109
+ * A JSONPath query to a specific point in a property (`marker` or an attribute) value string in a
110
+ * {@link MarkerObject} or {@link Usj}. The property cannot be `type` because `type`'s value has no
111
+ * representation in USFM.
112
+ *
113
+ * To represent a location in an attribute's key, use {@link UsjAttributeKeyLocation}.
114
+ *
115
+ * @public
116
+ */
117
+ export interface UsjPropertyValueLocation {
118
+ /**
119
+ * JSON path to the property the location is pointing to. The offset applies to this property's
120
+ * value string.
121
+ */
122
+ jsonPath: PropertyJsonPath;
123
+ /**
124
+ * The character index in the property's value string where this location is pointing. The
125
+ * location is at this offset within the property's value string.
126
+ */
127
+ propertyOffset: number;
128
+ }
129
+
130
+ /**
131
+ * A JSONPath query to a specific point in an attribute key string in a {@link MarkerObject} or
132
+ * {@link Usj}. The property cannot be `type` or `marker` because these properties' keys have no
133
+ * representation in USFM. The property also cannot be any special attribute whose key doesn't have
134
+ * a text representation in USFM like default attribute, leading attribute, text content attribute
135
+ *
136
+ * To represent a location in an attribute's value, use {@link UsjPropertyValueLocation}.
137
+ *
138
+ * @public
139
+ */
140
+ export interface UsjAttributeKeyLocation {
141
+ /**
142
+ * JSON path to the marker whose attribute key the location is pointing to. The offset applies to
143
+ * this attribute's key string unless the attribute is an attribute marker in USFM.
144
+ */
145
+ jsonPath: ContentJsonPath;
146
+ /** Attribute name on the marker object whose key this location is pointing to. */
147
+ keyName: string;
148
+ /**
149
+ * The character index in the attribute's key string where this location is pointing.
150
+ *
151
+ * If the attribute is an attribute marker in USFM, the location is at this offset within the
152
+ * marker name for this attribute marker (for example, `c`'s `altnumber` attribute has attribute
153
+ * marker `ca`, so its `keyOffset` applies to `ca`).
154
+ *
155
+ * If the attribute is not an attribute marker in USFM, the location is at this offset within the
156
+ * attribute's key string.
157
+ */
158
+ keyOffset: number;
159
+ }
160
+
161
+ /**
162
+ * A JSONPath query to an attribute marker derived from an attribute on a {@link MarkerObject} or
163
+ * {@link Usj}. Indicates the very beginning of that marker (at the backslash in USFM).
164
+ *
165
+ * @public
166
+ */
167
+ export interface UsjAttributeMarkerLocation {
168
+ /** JSON path to the marker whose attribute marker the location is pointing to. */
169
+ jsonPath: ContentJsonPath;
170
+ /**
171
+ * Attribute name on the marker object whose key this location is pointing to. This attribute is
172
+ * an attribute marker in USFM.
173
+ */
174
+ keyName: string;
175
+ }
176
+
177
+ /**
178
+ * A JSONPath query to a specific point in the closing marker representation of an attribute marker
179
+ * derived from an attribute on a {@link MarkerObject} or {@link Usj}.
180
+ *
181
+ * @public
182
+ */
183
+ export interface UsjClosingAttributeMarkerLocation {
184
+ /**
185
+ * JSON path to the marker whose attribute marker's closing marker the location is pointing to.
186
+ * The offset applies to the closing marker representation of that attribute marker (for example,
187
+ * `\ca*` in USFM).
188
+ */
189
+ jsonPath: ContentJsonPath;
190
+ /**
191
+ * Attribute name on the marker object whose key this location is pointing to. This attribute is
192
+ * an attribute marker in USFM.
193
+ */
194
+ keyName: string;
195
+ /**
196
+ * The character index in the closing marker representation where this location is pointing. The
197
+ * location is at this offset within the closing marker representation of the attribute marker.
198
+ */
199
+ keyClosingMarkerOffset: number;
200
+ }
201
+
202
+ /**
203
+ * JSON path to a {@link MarkerObject}, {@link Usj}, or text content string in the current USJ
204
+ * document.
205
+ *
206
+ * This could actually have more content clauses at the end, but TS types are limited
207
+ *
208
+ * @public
209
+ */
210
+ export type ContentJsonPath =
211
+ | ""
212
+ | `$`
213
+ | `$.content[${number}]`
214
+ | `$.content[${number}].content[${number}]`
215
+ | `$.content[${number}].content[${number}].content[${number}]`
216
+ | `$.content[${number}].content[${number}].content[${number}].content[${number}]`;
217
+
218
+ /**
219
+ * JSON path to the `marker` or an attribute on a {@link MarkerObject} or {@link Usj} in the current
220
+ * USJ document. Note that it seems you must use `['bracket notation']` rather than `.dot` notation
221
+ * if there are symbols other than underscore in the property name
222
+ *
223
+ * This could actually have more content clauses at the end, but TS types are limited
224
+ *
225
+ * @public
226
+ */
227
+ export type PropertyJsonPath =
228
+ | ""
229
+ | `$.${string}`
230
+ | `$['${string}']`
231
+ | `$.content[${number}].${string}`
232
+ | `$.content[${number}]['${string}']`
233
+ | `$.content[${number}].content[${number}].${string}`
234
+ | `$.content[${number}].content[${number}]['${string}']`
235
+ | `$.content[${number}].content[${number}].content[${number}].${string}`
236
+ | `$.content[${number}].content[${number}].content[${number}]['${string}']`
237
+ | `$.content[${number}].content[${number}].content[${number}].content[${number}].${string}`
238
+ | `$.content[${number}].content[${number}].content[${number}].content[${number}]['${string}']`;
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Type guards for {@link UsjDocumentLocation} subtypes.
3
+ *
4
+ * These guards enable runtime type narrowing to distinguish between the different
5
+ * location types in a USJ document.
6
+ */
7
+
8
+ import type {
9
+ UsjDocumentLocation,
10
+ UsjMarkerLocation,
11
+ UsjClosingMarkerLocation,
12
+ UsjTextContentLocation,
13
+ UsjPropertyValueLocation,
14
+ UsjAttributeKeyLocation,
15
+ UsjAttributeMarkerLocation,
16
+ UsjClosingAttributeMarkerLocation,
17
+ } from "./usj-document-location.model.js";
18
+
19
+ /**
20
+ * Type guard to check if a location is a {@link UsjMarkerLocation}.
21
+ *
22
+ * A marker location points to the very beginning of a marker (at the backslash in USFM).
23
+ * It only has a `jsonPath` property with no offset properties.
24
+ *
25
+ * @param location - The location to check.
26
+ * @returns `true` if the location is a `UsjMarkerLocation`, `false` otherwise.
27
+ *
28
+ * @public
29
+ */
30
+ export function isUsjMarkerLocation(
31
+ location: UsjDocumentLocation | undefined | null,
32
+ ): location is UsjMarkerLocation {
33
+ return (
34
+ location != null &&
35
+ "jsonPath" in location &&
36
+ !("offset" in location) &&
37
+ !("closingMarkerOffset" in location) &&
38
+ !("propertyOffset" in location) &&
39
+ !("keyName" in location)
40
+ );
41
+ }
42
+
43
+ /**
44
+ * Type guard to check if a location is a {@link UsjClosingMarkerLocation}.
45
+ *
46
+ * A closing marker location points to a specific point in the closing marker representation
47
+ * of a marker object (e.g., `\nd*` in USFM).
48
+ *
49
+ * @param location - The location to check.
50
+ * @returns `true` if the location is a `UsjClosingMarkerLocation`, `false` otherwise.
51
+ *
52
+ * @public
53
+ */
54
+ export function isUsjClosingMarkerLocation(
55
+ location: UsjDocumentLocation | undefined | null,
56
+ ): location is UsjClosingMarkerLocation {
57
+ return location != null && "jsonPath" in location && "closingMarkerOffset" in location;
58
+ }
59
+
60
+ /**
61
+ * Type guard to check if a location is a {@link UsjTextContentLocation}.
62
+ *
63
+ * A text content location points to a specific character offset within a text content string
64
+ * in a marker's content array.
65
+ *
66
+ * @param location - The location to check.
67
+ * @returns `true` if the location is a `UsjTextContentLocation`, `false` otherwise.
68
+ *
69
+ * @public
70
+ */
71
+ export function isUsjTextContentLocation(
72
+ location: UsjDocumentLocation | undefined | null,
73
+ ): location is UsjTextContentLocation {
74
+ return (
75
+ location != null &&
76
+ "jsonPath" in location &&
77
+ "offset" in location &&
78
+ !("propertyOffset" in location) &&
79
+ !("keyName" in location)
80
+ );
81
+ }
82
+
83
+ /**
84
+ * Type guard to check if a location is a {@link UsjPropertyValueLocation}.
85
+ *
86
+ * A property value location points to a specific character offset within a property value
87
+ * (such as `marker` or an attribute value).
88
+ *
89
+ * @param location - The location to check.
90
+ * @returns `true` if the location is a `UsjPropertyValueLocation`, `false` otherwise.
91
+ *
92
+ * @public
93
+ */
94
+ export function isUsjPropertyValueLocation(
95
+ location: UsjDocumentLocation | undefined | null,
96
+ ): location is UsjPropertyValueLocation {
97
+ return location != null && "jsonPath" in location && "propertyOffset" in location;
98
+ }
99
+
100
+ /**
101
+ * Type guard to check if a location is a {@link UsjAttributeKeyLocation}.
102
+ *
103
+ * An attribute key location points to a specific character offset within an attribute's key string.
104
+ *
105
+ * @param location - The location to check.
106
+ * @returns `true` if the location is a `UsjAttributeKeyLocation`, `false` otherwise.
107
+ *
108
+ * @public
109
+ */
110
+ export function isUsjAttributeKeyLocation(
111
+ location: UsjDocumentLocation | undefined | null,
112
+ ): location is UsjAttributeKeyLocation {
113
+ return (
114
+ location != null && "jsonPath" in location && "keyName" in location && "keyOffset" in location
115
+ );
116
+ }
117
+
118
+ /**
119
+ * Type guard to check if a location is a {@link UsjAttributeMarkerLocation}.
120
+ *
121
+ * An attribute marker location points to the beginning of an attribute marker
122
+ * (at the backslash in USFM, e.g., `\ca` for chapter alternate number).
123
+ *
124
+ * @param location - The location to check.
125
+ * @returns `true` if the location is a `UsjAttributeMarkerLocation`, `false` otherwise.
126
+ *
127
+ * @public
128
+ */
129
+ export function isUsjAttributeMarkerLocation(
130
+ location: UsjDocumentLocation | undefined | null,
131
+ ): location is UsjAttributeMarkerLocation {
132
+ return (
133
+ location != null &&
134
+ "jsonPath" in location &&
135
+ "keyName" in location &&
136
+ !("keyOffset" in location) &&
137
+ !("keyClosingMarkerOffset" in location)
138
+ );
139
+ }
140
+
141
+ /**
142
+ * Type guard to check if a location is a {@link UsjClosingAttributeMarkerLocation}.
143
+ *
144
+ * A closing attribute marker location points to a specific point in the closing marker
145
+ * representation of an attribute marker (e.g., `\ca*` in USFM).
146
+ *
147
+ * @param location - The location to check.
148
+ * @returns `true` if the location is a `UsjClosingAttributeMarkerLocation`, `false` otherwise.
149
+ *
150
+ * @public
151
+ */
152
+ export function isUsjClosingAttributeMarkerLocation(
153
+ location: UsjDocumentLocation | undefined | null,
154
+ ): location is UsjClosingAttributeMarkerLocation {
155
+ return (
156
+ location != null &&
157
+ "jsonPath" in location &&
158
+ "keyName" in location &&
159
+ "keyClosingMarkerOffset" in location
160
+ );
161
+ }
162
+
163
+ /**
164
+ * Gets a human-readable name for the type of a {@link UsjDocumentLocation}.
165
+ *
166
+ * Useful for error messages when an unsupported location type is encountered.
167
+ *
168
+ * @param location - The location to get the type name for.
169
+ * @returns A string describing the location type, or "undefined" / "null" if the location is not
170
+ * provided.
171
+ *
172
+ * @public
173
+ */
174
+ export function getUsjDocumentLocationTypeName(
175
+ location: UsjDocumentLocation | undefined | null,
176
+ ): string {
177
+ if (location === undefined) return "undefined";
178
+ if (location === null) return "null";
179
+ if (isUsjClosingAttributeMarkerLocation(location)) return "UsjClosingAttributeMarkerLocation";
180
+ if (isUsjAttributeKeyLocation(location)) return "UsjAttributeKeyLocation";
181
+ if (isUsjAttributeMarkerLocation(location)) return "UsjAttributeMarkerLocation";
182
+ if (isUsjPropertyValueLocation(location)) return "UsjPropertyValueLocation";
183
+ if (isUsjClosingMarkerLocation(location)) return "UsjClosingMarkerLocation";
184
+ if (isUsjTextContentLocation(location)) return "UsjTextContentLocation";
185
+ if (isUsjMarkerLocation(location)) return "UsjMarkerLocation";
186
+ return "Unknown";
187
+ }
package/src/index.ts CHANGED
@@ -3,9 +3,25 @@
3
3
  * Utilities for Scripture data conversion and manipulation, including USJ/USX format conversion.
4
4
  */
5
5
 
6
+ export type * from "./converters/usj/usj-document-location.model.js";
6
7
  export type { Usj, BookCode, MarkerContent, MarkerObject } from "./converters/usj/usj.model.js";
7
8
 
8
9
  export { assertSafeKey } from "./converters/usj/converter.utils.js";
10
+ export {
11
+ indexesFromUsjJsonPath,
12
+ usjJsonPathFromIndexes,
13
+ } from "./converters/usj/jsonpath-indexes.js";
14
+ export {
15
+ getUsjDocumentLocationTypeName,
16
+ isUsjAttributeKeyLocation,
17
+ isUsjAttributeMarkerLocation,
18
+ isUsjClosingAttributeMarkerLocation,
19
+ isUsjClosingMarkerLocation,
20
+ isUsjMarkerLocation,
21
+ isUsjPropertyValueLocation,
22
+ isUsjTextContentLocation,
23
+ } from "./converters/usj/usj-document-location.utils.js";
24
+ export { usjToUsxString } from "./converters/usj/usj-to-usx.js";
9
25
  export {
10
26
  EMPTY_USJ,
11
27
  MARKER_OBJECT_PROPS,
@@ -14,10 +30,5 @@ export {
14
30
  isValidBookCode,
15
31
  VALID_BOOK_CODES,
16
32
  } from "./converters/usj/usj.model.js";
17
- export { EMPTY_USX, USX_TYPE, USX_VERSION } from "./converters/usj/usx.model.js";
18
33
  export { usxStringToUsj } from "./converters/usj/usx-to-usj.js";
19
- export { usjToUsxString } from "./converters/usj/usj-to-usx.js";
20
- export {
21
- indexesFromUsjJsonPath,
22
- usjJsonPathFromIndexes,
23
- } from "./converters/usj/jsonpath-indexes.js";
34
+ export { EMPTY_USX, USX_TYPE, USX_VERSION } from "./converters/usj/usx.model.js";