mdld-parse 0.7.2 → 0.7.4
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/package.json +1 -1
- package/src/constants.js +30 -0
- package/src/generate.js +37 -53
- package/src/index.js +1 -1
- package/src/locate.js +2 -17
- package/src/merge.js +4 -5
- package/src/parse.js +222 -282
- package/src/render.js +320 -357
- package/src/shared.js +529 -0
- package/src/utils.js +2 -9
package/src/shared.js
ADDED
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
import { DEFAULT_CONTEXT, STANDALONE_SUBJECT_REGEX, FENCE_REGEX, PREFIX_REGEX, HEADING_REGEX, UNORDERED_LIST_REGEX, BLOCKQUOTE_REGEX } from './constants.js';
|
|
2
|
+
import { parseSemanticBlock, expandIRI, shortenIRI } from './utils.js';
|
|
3
|
+
|
|
4
|
+
// Cache for fence regex patterns
|
|
5
|
+
export const FENCE_CLOSE_PATTERNS = new Map();
|
|
6
|
+
|
|
7
|
+
export function getFenceClosePattern(fenceChar) {
|
|
8
|
+
if (!FENCE_CLOSE_PATTERNS.has(fenceChar)) {
|
|
9
|
+
FENCE_CLOSE_PATTERNS.set(fenceChar, new RegExp(`^(${fenceChar}{3,})`));
|
|
10
|
+
}
|
|
11
|
+
return FENCE_CLOSE_PATTERNS.get(fenceChar);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Range calculation utilities - shared between parser and renderer
|
|
15
|
+
export function calcRangeInfo(line, attrs, lineStart, prefixLength, valueLength) {
|
|
16
|
+
const wsLength = prefixLength < line.length && line[prefixLength] === ' ' ? 1 :
|
|
17
|
+
line.slice(prefixLength).match(/^\s+/)?.[0]?.length || 0;
|
|
18
|
+
const valueStartInLine = prefixLength + wsLength;
|
|
19
|
+
return {
|
|
20
|
+
valueRange: [lineStart + valueStartInLine, lineStart + valueStartInLine + valueLength],
|
|
21
|
+
attrsRange: calcAttrsRange(line, attrs, lineStart)
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function calcAttrsRange(line, attrs, lineStart) {
|
|
26
|
+
if (!attrs) return null;
|
|
27
|
+
const attrsStartInLine = line.lastIndexOf(attrs);
|
|
28
|
+
return attrsStartInLine >= 0 ? [lineStart + attrsStartInLine, lineStart + attrsStartInLine + attrs.length] : null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Token creation utilities - shared structure
|
|
32
|
+
export function createToken(type, range, text, attrs = null, attrsRange = null, valueRange = null, extra = {}) {
|
|
33
|
+
const token = { type, range, text, attrs, attrsRange, valueRange, ...extra };
|
|
34
|
+
Object.defineProperty(token, '_carriers', {
|
|
35
|
+
enumerable: false, writable: true, value: null
|
|
36
|
+
});
|
|
37
|
+
return token;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function createCarrier(type, text, attrs, attrsRange, valueRange, range, pos, extra = {}) {
|
|
41
|
+
return { type, text, attrs, attrsRange, valueRange, range, pos, ...extra };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// List token creation - shared logic
|
|
45
|
+
export function createListToken(type, line, lineStart, pos, match) {
|
|
46
|
+
const attrs = match[4] || null;
|
|
47
|
+
const prefix = match[1].length + (match[2] ? match[2].length : 0);
|
|
48
|
+
const rangeInfo = calcRangeInfo(line, attrs, lineStart, prefix, match[3].length);
|
|
49
|
+
return createToken(type, [lineStart, pos - 1], match[3].trim(), attrs,
|
|
50
|
+
rangeInfo.attrsRange, rangeInfo.valueRange, { indent: match[1].length });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Semantic block parsing - shared between parser and renderer
|
|
54
|
+
export const semCache = {};
|
|
55
|
+
export const EMPTY_SEM = Object.freeze({ predicates: [], types: [], subject: null });
|
|
56
|
+
|
|
57
|
+
export function parseSemCached(attrs) {
|
|
58
|
+
if (!attrs) return EMPTY_SEM;
|
|
59
|
+
let sem = semCache[attrs];
|
|
60
|
+
if (!sem) {
|
|
61
|
+
sem = Object.freeze(parseSemanticBlock(attrs));
|
|
62
|
+
semCache[attrs] = sem;
|
|
63
|
+
}
|
|
64
|
+
return sem;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Indentation utilities - shared for list processing
|
|
68
|
+
export function getIndentLevel(block, sourceText) {
|
|
69
|
+
if (!block.range || !sourceText) return 0;
|
|
70
|
+
|
|
71
|
+
const text = sourceText.substring(block.range.start, block.range.end);
|
|
72
|
+
const indentMatch = text.match(/^(\s*)/);
|
|
73
|
+
const indentSpaces = indentMatch ? indentMatch[1].length : 0;
|
|
74
|
+
|
|
75
|
+
// CommonMark: 4 spaces or 1 tab = one level
|
|
76
|
+
// We'll use 2 spaces for better readability (configurable)
|
|
77
|
+
return Math.floor(indentSpaces / 2);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Content extraction utilities - shared between parser and renderer
|
|
81
|
+
export function extractContentFromRange(sourceText, range, attrsRange = null) {
|
|
82
|
+
if (!range || !sourceText) return '';
|
|
83
|
+
|
|
84
|
+
let text = sourceText.substring(range[0], range[1]);
|
|
85
|
+
|
|
86
|
+
// Remove MD-LD annotations, preserve content
|
|
87
|
+
if (attrsRange) {
|
|
88
|
+
const beforeAttrs = text.substring(0, attrsRange[0] - range[0]);
|
|
89
|
+
const afterAttrs = text.substring(attrsRange[1] - range[0]);
|
|
90
|
+
text = beforeAttrs + afterAttrs;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return text.trim();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// List marker utilities - shared for advanced list processing
|
|
97
|
+
export function getListMarker(block, sourceText) {
|
|
98
|
+
if (!block.range) return null;
|
|
99
|
+
|
|
100
|
+
const text = sourceText.substring(block.range.start, block.range.end);
|
|
101
|
+
const markerMatch = text.match(/^(\s*)([-*+]|\d+\[\.|\])\s+/);
|
|
102
|
+
|
|
103
|
+
if (!markerMatch) return null;
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
type: markerMatch[2].startsWith('-') ? 'dash' :
|
|
107
|
+
markerMatch[2].startsWith('*') ? 'asterisk' :
|
|
108
|
+
markerMatch[2].startsWith('+') ? 'plus' : 'ordered',
|
|
109
|
+
marker: markerMatch[2],
|
|
110
|
+
indent: markerMatch[1].length
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// CommonMark line processors - shared between parser and renderer
|
|
115
|
+
export const PROCESSORS = [
|
|
116
|
+
{ test: line => line.startsWith('```'), process: null }, // Code blocks handled separately
|
|
117
|
+
{ test: line => line.startsWith('`'), process: null }, // Code spans handled separately
|
|
118
|
+
{ test: line => PREFIX_REGEX.test(line), process: null }, // Prefixes handled separately
|
|
119
|
+
{ test: line => HEADING_REGEX.test(line), process: null }, // Headings handled separately
|
|
120
|
+
{ test: line => UNORDERED_LIST_REGEX.test(line), process: null }, // Lists handled separately
|
|
121
|
+
{ test: line => BLOCKQUOTE_REGEX.test(line), process: null }, // Blockquotes handled separately
|
|
122
|
+
{ test: line => STANDALONE_SUBJECT_REGEX.test(line), process: null }, // Standalone subjects handled separately
|
|
123
|
+
{ test: line => line.trim() === '', process: null }, // Empty lines handled separately
|
|
124
|
+
{ test: line => true, process: null } // Default: paragraph
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
// Token scanning processors - shared between parser and renderer
|
|
128
|
+
export const TOKEN_PROCESSORS = [
|
|
129
|
+
{ type: 'fence', test: line => FENCE_REGEX.test(line.trim()), process: null }, // Will be overridden in parse.js
|
|
130
|
+
{ type: 'content', test: line => false, process: null }, // Will be overridden in parse.js
|
|
131
|
+
{ type: 'prefix', test: line => PREFIX_REGEX.test(line), process: null }, // Will be overridden in parse.js
|
|
132
|
+
{ type: 'heading', test: line => HEADING_REGEX.test(line), process: null }, // Will be overridden in parse.js
|
|
133
|
+
{ type: 'list', test: line => UNORDERED_LIST_REGEX.test(line), process: null }, // Will be overridden in parse.js
|
|
134
|
+
{ type: 'blockquote', test: line => BLOCKQUOTE_REGEX.test(line), process: null }, // Will be overridden in parse.js
|
|
135
|
+
{ type: 'para', test: line => line.trim(), process: null } // Will be overridden in parse.js
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
// Language and attributes parsing
|
|
139
|
+
export function parseLangAndAttrs(langAndAttrs) {
|
|
140
|
+
const spaceIndex = langAndAttrs.indexOf(' ');
|
|
141
|
+
const braceIndex = langAndAttrs.indexOf('{');
|
|
142
|
+
const langEnd = Math.min(
|
|
143
|
+
spaceIndex > -1 ? spaceIndex : Infinity,
|
|
144
|
+
braceIndex > -1 ? braceIndex : Infinity
|
|
145
|
+
);
|
|
146
|
+
return {
|
|
147
|
+
lang: langAndAttrs.substring(0, langEnd),
|
|
148
|
+
attrsText: langAndAttrs.substring(langEnd).match(/\{[^{}]*\}/)?.[0] || null
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Carrier extraction utilities
|
|
153
|
+
export function findMatchingBracket(text, bracketStart) {
|
|
154
|
+
let bracketDepth = 1;
|
|
155
|
+
let bracketEnd = bracketStart + 1;
|
|
156
|
+
|
|
157
|
+
while (bracketEnd < text.length && bracketDepth > 0) {
|
|
158
|
+
if (text[bracketEnd] === '[') bracketDepth++;
|
|
159
|
+
else if (text[bracketEnd] === ']') bracketDepth--;
|
|
160
|
+
bracketEnd++;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return bracketDepth > 0 ? null : bracketEnd;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function extractUrlFromBrackets(text, bracketEnd) {
|
|
167
|
+
let url = null;
|
|
168
|
+
let spanEnd = bracketEnd;
|
|
169
|
+
|
|
170
|
+
if (text[spanEnd] === '(') {
|
|
171
|
+
const parenEnd = text.indexOf(')', spanEnd);
|
|
172
|
+
if (parenEnd !== -1) {
|
|
173
|
+
url = text.substring(spanEnd + 1, parenEnd);
|
|
174
|
+
spanEnd = parenEnd + 1;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return { url, spanEnd };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function extractAttributesFromText(text, spanEnd, baseOffset) {
|
|
182
|
+
let attrs = null;
|
|
183
|
+
let attrsRange = null;
|
|
184
|
+
const remaining = text.substring(spanEnd);
|
|
185
|
+
|
|
186
|
+
const wsMatch = remaining.match(/^\s+/);
|
|
187
|
+
const attrsStart = wsMatch ? wsMatch[0].length : 0;
|
|
188
|
+
|
|
189
|
+
if (remaining[attrsStart] === '{') {
|
|
190
|
+
const braceEnd = remaining.indexOf('}', attrsStart);
|
|
191
|
+
if (braceEnd !== -1) {
|
|
192
|
+
attrs = remaining.substring(attrsStart, braceEnd + 1);
|
|
193
|
+
const absStart = baseOffset + spanEnd + attrsStart;
|
|
194
|
+
attrsRange = [absStart, absStart + attrs.length];
|
|
195
|
+
spanEnd += braceEnd + 1;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return { attrs, attrsRange, finalSpanEnd: spanEnd };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function determineCarrierType(url) {
|
|
203
|
+
if (url && !url.startsWith('=')) {
|
|
204
|
+
return { carrierType: 'link', resourceIRI: url };
|
|
205
|
+
}
|
|
206
|
+
return { carrierType: 'span', resourceIRI: null };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function calcCarrierRanges(match, baseOffset, matchStart) {
|
|
210
|
+
const valueStart = baseOffset + matchStart + match[0].indexOf(match[1]);
|
|
211
|
+
const valueEnd = valueStart + match[1].length;
|
|
212
|
+
const attrsStart = baseOffset + matchStart + match[0].indexOf('{');
|
|
213
|
+
const attrsEnd = attrsStart + match[2].length + 2; // +2 for { and }
|
|
214
|
+
return {
|
|
215
|
+
valueRange: [valueStart, valueEnd],
|
|
216
|
+
attrsRange: [attrsStart + 1, attrsEnd - 1], // Exclude braces
|
|
217
|
+
range: [baseOffset + matchStart, attrsEnd],
|
|
218
|
+
pos: matchStart + match[0].length // pos should be relative to current text, not document
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Clean text extraction utilities
|
|
223
|
+
export function extractCleanText(token) {
|
|
224
|
+
if (!token.text) return '';
|
|
225
|
+
|
|
226
|
+
let text = token.text;
|
|
227
|
+
|
|
228
|
+
// Remove semantic annotations
|
|
229
|
+
if (token.attrsRange) {
|
|
230
|
+
const beforeAttrs = text.substring(0, token.attrsRange[0] - (token.range?.[0] || 0));
|
|
231
|
+
const afterAttrs = text.substring(token.attrsRange[1] - (token.range?.[0] || 0));
|
|
232
|
+
text = beforeAttrs + afterAttrs;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Clean based on token type
|
|
236
|
+
switch (token.type) {
|
|
237
|
+
case 'heading':
|
|
238
|
+
return text.replace(/^#+\s*/, '').trim();
|
|
239
|
+
case 'list':
|
|
240
|
+
return text.replace(/^[-*+]\s*/, '').trim();
|
|
241
|
+
case 'blockquote':
|
|
242
|
+
return text.replace(/^>\s*/, '').trim();
|
|
243
|
+
default:
|
|
244
|
+
return text.trim();
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Quad emission utilities
|
|
249
|
+
export const RDF_TYPE = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
|
|
250
|
+
export const RDF_STATEMENT = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#Statement';
|
|
251
|
+
export const RDF_SUBJECT = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#subject';
|
|
252
|
+
export const RDF_PREDICATE = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#predicate';
|
|
253
|
+
export const RDF_OBJECT = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#object';
|
|
254
|
+
|
|
255
|
+
export function createLeanOriginEntry(block, subject, predicate, meta = null) {
|
|
256
|
+
return {
|
|
257
|
+
blockId: block.id,
|
|
258
|
+
range: block.range,
|
|
259
|
+
carrierType: block.carrierType,
|
|
260
|
+
subject: subject.value,
|
|
261
|
+
predicate: predicate.value,
|
|
262
|
+
context: block.context, // Direct reference instead of spread
|
|
263
|
+
polarity: meta?.remove ? '-' : '+',
|
|
264
|
+
value: block.text || ''
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Fragment resolution utilities
|
|
269
|
+
export function resolveFragment(fragment, currentSubject, dataFactory) {
|
|
270
|
+
if (!currentSubject) return null;
|
|
271
|
+
const subjectValue = currentSubject.value;
|
|
272
|
+
const hashIndex = subjectValue.indexOf('#');
|
|
273
|
+
const baseIRI = hashIndex > -1 ? subjectValue.slice(0, hashIndex) : subjectValue;
|
|
274
|
+
return dataFactory.namedNode(baseIRI + '#' + fragment);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export function resolveSubject(sem, state) {
|
|
278
|
+
if (!sem.subject) return null;
|
|
279
|
+
if (sem.subject === 'RESET') {
|
|
280
|
+
state.currentSubject = null;
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
if (sem.subject.startsWith('=#')) return resolveFragment(sem.subject.substring(2), state.currentSubject, state.df);
|
|
284
|
+
return state.df.namedNode(expandIRI(sem.subject, state.ctx));
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export function resolveObject(sem, state) {
|
|
288
|
+
if (!sem.object) return null;
|
|
289
|
+
if (sem.object.startsWith('#')) return resolveFragment(sem.object.substring(1), state.currentSubject, state.df);
|
|
290
|
+
return state.df.namedNode(expandIRI(sem.object, state.ctx));
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// HTML escaping - shared utility
|
|
294
|
+
export function escapeHtml(text) {
|
|
295
|
+
if (!text) return '';
|
|
296
|
+
return text
|
|
297
|
+
.replace(/&/g, '&')
|
|
298
|
+
.replace(/</g, '<')
|
|
299
|
+
.replace(/>/g, '>')
|
|
300
|
+
.replace(/"/g, '"')
|
|
301
|
+
.replace(/'/g, ''');
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// RDF term type checking utilities - shared across modules
|
|
305
|
+
export function isLiteral(term) {
|
|
306
|
+
return term?.termType === 'Literal';
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export function isNamedNode(term) {
|
|
310
|
+
return term?.termType === 'NamedNode';
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export function isRdfType(term) {
|
|
314
|
+
return term?.value === 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// IRI prefix extraction utility
|
|
318
|
+
export function getPrefixFromIRI(iri, context) {
|
|
319
|
+
if (!iri) return null;
|
|
320
|
+
const shortened = shortenIRI(iri, context);
|
|
321
|
+
if (shortened.includes(':')) {
|
|
322
|
+
return shortened.split(':')[0];
|
|
323
|
+
}
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Prefix collection utility - used by generate.js
|
|
328
|
+
export function collectUsedPrefixes(subjectGroups, context) {
|
|
329
|
+
const usedPrefixes = new Set();
|
|
330
|
+
|
|
331
|
+
for (const subjectQuads of subjectGroups.values()) {
|
|
332
|
+
for (const quad of subjectQuads) {
|
|
333
|
+
// Check subject prefix
|
|
334
|
+
const subjectPrefix = getPrefixFromIRI(quad.subject.value, context);
|
|
335
|
+
if (subjectPrefix) usedPrefixes.add(subjectPrefix);
|
|
336
|
+
|
|
337
|
+
// Check predicate prefix
|
|
338
|
+
const predicatePrefix = getPrefixFromIRI(quad.predicate.value, context);
|
|
339
|
+
if (predicatePrefix) usedPrefixes.add(predicatePrefix);
|
|
340
|
+
|
|
341
|
+
// Check object prefix if it's a named node
|
|
342
|
+
if (isNamedNode(quad.object)) {
|
|
343
|
+
const objectPrefix = getPrefixFromIRI(quad.object.value, context);
|
|
344
|
+
if (objectPrefix) usedPrefixes.add(objectPrefix);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Check datatype prefix if present
|
|
348
|
+
if (quad.object.datatype && quad.object.datatype.value) {
|
|
349
|
+
const datatypePrefix = getPrefixFromIRI(quad.object.datatype.value, context);
|
|
350
|
+
if (datatypePrefix) usedPrefixes.add(datatypePrefix);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return usedPrefixes;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Token processing utility - eliminates duplication in TOKEN_PROCESSORS
|
|
359
|
+
export function processTokenWithBlockTracking(token, state, processAnnotations, createBlockEntry, additionalProcessors = []) {
|
|
360
|
+
const blockEntry = createBlockEntry(token, state);
|
|
361
|
+
state.currentBlock = blockEntry;
|
|
362
|
+
state.blockStack.push(blockEntry.id);
|
|
363
|
+
|
|
364
|
+
// Run any additional processors first
|
|
365
|
+
additionalProcessors.forEach(processor => processor(token, state));
|
|
366
|
+
|
|
367
|
+
// Process annotations
|
|
368
|
+
processAnnotations(token, state, token.type);
|
|
369
|
+
|
|
370
|
+
state.blockStack.pop();
|
|
371
|
+
state.currentBlock = state.blockStack.length > 0 ?
|
|
372
|
+
state.origin.blocks.get(state.blockStack[state.blockStack.length - 1]) : null;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Quad key generation - shared between parser and renderer
|
|
376
|
+
export function quadIndexKey(subject, predicate, object) {
|
|
377
|
+
const datatype = object.datatype?.value || '';
|
|
378
|
+
const language = object.language || '';
|
|
379
|
+
return `${subject.value}|${predicate.value}|${object.value}|${datatype}|${language}`;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// IRI expansion and shortening - shared utilities
|
|
383
|
+
export function expandAndShortenIRI(iri, ctx) {
|
|
384
|
+
const expanded = expandIRI(iri, ctx);
|
|
385
|
+
return shortenIRI(expanded, ctx);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Subject resolution utilities - shared between parser and renderer
|
|
389
|
+
export function resolveSubjectType(subjectDecl) {
|
|
390
|
+
if (!subjectDecl) return 'none';
|
|
391
|
+
|
|
392
|
+
if (subjectDecl.startsWith('=#')) {
|
|
393
|
+
return 'fragment';
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (subjectDecl.startsWith('+')) {
|
|
397
|
+
return 'soft-object';
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (subjectDecl === 'RESET') {
|
|
401
|
+
return 'reset';
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return 'full-iri';
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Constants - shared across modules (bundle-size optimized)
|
|
408
|
+
export const XSD_STRING = 'http://www.w3.org/2001/XMLSchema#string';
|
|
409
|
+
|
|
410
|
+
// Optimized sorting utilities - inline for better minification
|
|
411
|
+
export function sortQuadsByPredicate(quads) {
|
|
412
|
+
return quads.sort((a, b) => a.predicate.value.localeCompare(b.predicate.value));
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Optimized text generation - template literals for smaller bundle
|
|
416
|
+
export const generatePrefixDeclaration = (prefix, namespace) => `[${prefix}] <${namespace}>\n`;
|
|
417
|
+
|
|
418
|
+
export function generateLiteralText(quad, context) {
|
|
419
|
+
const predShort = shortenIRI(quad.predicate.value, context);
|
|
420
|
+
let annotation = predShort;
|
|
421
|
+
|
|
422
|
+
if (quad.object.language) {
|
|
423
|
+
annotation += ` @${quad.object.language}`;
|
|
424
|
+
} else if (quad.object.datatype.value !== XSD_STRING) {
|
|
425
|
+
annotation += ` ^^${shortenIRI(quad.object.datatype.value, context)}`;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return `[${quad.object.value}] {${annotation}}\n`;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export const generateObjectText = (quad, context) => {
|
|
432
|
+
const objShort = shortenIRI(quad.object.value, context);
|
|
433
|
+
const predShort = shortenIRI(quad.predicate.value, context);
|
|
434
|
+
return `[${objShort}] {+${objShort} ?${predShort}}\n`;
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
// Optimized quad filtering - destructuring for smaller minified output
|
|
438
|
+
export function filterQuadsByType(subjectQuads) {
|
|
439
|
+
const types = [], literals = [], objects = [];
|
|
440
|
+
for (const q of subjectQuads) {
|
|
441
|
+
if (isRdfType(q.predicate)) {
|
|
442
|
+
types.push(q);
|
|
443
|
+
} else if (isLiteral(q.object)) {
|
|
444
|
+
literals.push(q);
|
|
445
|
+
} else if (isNamedNode(q.object)) {
|
|
446
|
+
objects.push(q);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return { types, literals, objects };
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Predicate processing utilities - common RDFa patterns
|
|
453
|
+
export function processPredicates(predicates, ctx) {
|
|
454
|
+
const literalProps = [];
|
|
455
|
+
const objectProps = [];
|
|
456
|
+
const reverseProps = [];
|
|
457
|
+
|
|
458
|
+
predicates.forEach(pred => {
|
|
459
|
+
const iri = typeof pred === 'string' ? pred : pred.iri;
|
|
460
|
+
const expanded = expandIRI(iri, ctx);
|
|
461
|
+
const shortened = shortenIRI(expanded, ctx);
|
|
462
|
+
const form = typeof pred === 'string' ? '' : (pred.form || '');
|
|
463
|
+
|
|
464
|
+
if (form === '!') {
|
|
465
|
+
reverseProps.push(shortened);
|
|
466
|
+
} else if (form === '?') {
|
|
467
|
+
objectProps.push(shortened);
|
|
468
|
+
} else {
|
|
469
|
+
literalProps.push(shortened);
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
return { literalProps, objectProps, reverseProps };
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Deterministic sorting utilities - ensure consistent output
|
|
477
|
+
export function sortDeterministic(array, keyFn) {
|
|
478
|
+
return array.sort((a, b) => {
|
|
479
|
+
const keyA = keyFn(a);
|
|
480
|
+
const keyB = keyFn(b);
|
|
481
|
+
return keyA.localeCompare(keyB);
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
export function sortQuadsDeterministically(quads) {
|
|
486
|
+
return quads.sort((a, b) => {
|
|
487
|
+
// Deterministic sorting: subject -> predicate -> object
|
|
488
|
+
const sComp = a.subject.value.localeCompare(b.subject.value);
|
|
489
|
+
if (sComp !== 0) return sComp;
|
|
490
|
+
const pComp = a.predicate.value.localeCompare(b.predicate.value);
|
|
491
|
+
if (pComp !== 0) return pComp;
|
|
492
|
+
const oA = isLiteral(a.object) ? a.object.value : a.object.value;
|
|
493
|
+
const oB = isLiteral(b.object) ? b.object.value : b.object.value;
|
|
494
|
+
return oA.localeCompare(oB);
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Optimized deterministic prefix generation
|
|
499
|
+
export function generateDeterministicPrefixes(context, usedPrefixes) {
|
|
500
|
+
const sortedEntries = Object.entries(context).sort(([a], [b]) => a.localeCompare(b));
|
|
501
|
+
let text = '';
|
|
502
|
+
|
|
503
|
+
for (const [prefix, namespace] of sortedEntries) {
|
|
504
|
+
if (prefix !== '@vocab' && !prefix.startsWith('@') && !DEFAULT_CONTEXT[prefix] && usedPrefixes.has(prefix)) {
|
|
505
|
+
text += generatePrefixDeclaration(prefix, namespace);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
return text;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// Memory-efficient block creation
|
|
513
|
+
export function createOptimizedBlockEntry(token, state) {
|
|
514
|
+
const id = hash(`${token.range[0]}-${token.range[1]}-${token.text.slice(0, 50)}`);
|
|
515
|
+
const block = {
|
|
516
|
+
id,
|
|
517
|
+
type: token.type,
|
|
518
|
+
carrierType: token.type,
|
|
519
|
+
range: token.range,
|
|
520
|
+
text: token.text,
|
|
521
|
+
carriers: [],
|
|
522
|
+
predicates: [],
|
|
523
|
+
subject: state.currentSubject,
|
|
524
|
+
context: { ...state.ctx }
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
state.origin.blocks.set(id, block);
|
|
528
|
+
return block;
|
|
529
|
+
}
|
package/src/utils.js
CHANGED
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
'@vocab': "http://www.w3.org/2000/01/rdf-schema#",
|
|
3
|
-
rdf: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
|
|
4
|
-
rdfs: 'http://www.w3.org/2000/01/rdf-schema#',
|
|
5
|
-
xsd: 'http://www.w3.org/2001/XMLSchema#',
|
|
6
|
-
sh: "http://www.w3.org/ns/shacl#",
|
|
7
|
-
prov: 'http://www.w3.org/ns/prov#'
|
|
8
|
-
};
|
|
1
|
+
import { URL_REGEX, DEFAULT_CONTEXT } from './constants.js';
|
|
9
2
|
|
|
10
3
|
// Base Term class for RDF/JS compatibility
|
|
11
4
|
export class Term {
|
|
@@ -258,7 +251,7 @@ export function expandIRI(term, ctx) {
|
|
|
258
251
|
const t = raw.trim();
|
|
259
252
|
let result;
|
|
260
253
|
|
|
261
|
-
if (t.match(
|
|
254
|
+
if (t.match(URL_REGEX)) {
|
|
262
255
|
result = t;
|
|
263
256
|
} else if (t.includes(':')) {
|
|
264
257
|
const [prefix, ref] = t.split(':', 2);
|