@openuji/speculator 0.7.4 → 0.8.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.
- package/README.md +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/parse/assembler.d.ts +1 -1
- package/dist/parse/assembler.d.ts.map +1 -1
- package/dist/parse/assembler.js +67 -13
- package/dist/parse/assembler.js.map +1 -1
- package/dist/parse/html/AsideHtmlParser.d.ts.map +1 -1
- package/dist/parse/html/AsideHtmlParser.js +85 -3
- package/dist/parse/html/AsideHtmlParser.js.map +1 -1
- package/dist/parse/html/CodeHtmlParser.d.ts.map +1 -1
- package/dist/parse/html/CodeHtmlParser.js +6 -2
- package/dist/parse/html/CodeHtmlParser.js.map +1 -1
- package/dist/parse/html/HeadingsHtmlParser.d.ts.map +1 -1
- package/dist/parse/html/HeadingsHtmlParser.js +3 -4
- package/dist/parse/html/HeadingsHtmlParser.js.map +1 -1
- package/dist/parse/html/IdlHtmlParser.d.ts +0 -4
- package/dist/parse/html/IdlHtmlParser.d.ts.map +1 -1
- package/dist/parse/html/IdlHtmlParser.js +2 -138
- package/dist/parse/html/IdlHtmlParser.js.map +1 -1
- package/dist/parse/html/MiscHtmlParser.d.ts.map +1 -1
- package/dist/parse/html/MiscHtmlParser.js +2 -42
- package/dist/parse/html/MiscHtmlParser.js.map +1 -1
- package/dist/parse/html/SectionsHtmlParser.d.ts.map +1 -1
- package/dist/parse/html/SectionsHtmlParser.js +17 -26
- package/dist/parse/html/SectionsHtmlParser.js.map +1 -1
- package/dist/parse/html/SpecStatementHtmlParser.js +3 -0
- package/dist/parse/html/SpecStatementHtmlParser.js.map +1 -1
- package/dist/parse/html/VocabHtmlParser.d.ts +12 -0
- package/dist/parse/html/VocabHtmlParser.d.ts.map +1 -0
- package/dist/parse/html/VocabHtmlParser.js +1156 -0
- package/dist/parse/html/VocabHtmlParser.js.map +1 -0
- package/dist/parse/html/parser.d.ts +13 -4
- package/dist/parse/html/parser.d.ts.map +1 -1
- package/dist/parse/html/parser.js +105 -119
- package/dist/parse/html/parser.js.map +1 -1
- package/dist/parse/index.d.ts +1 -2
- package/dist/parse/index.d.ts.map +1 -1
- package/dist/parse/index.js +0 -1
- package/dist/parse/index.js.map +1 -1
- package/dist/parse/markdown/CodeMarkdownParser.d.ts.map +1 -1
- package/dist/parse/markdown/CodeMarkdownParser.js +20 -2
- package/dist/parse/markdown/CodeMarkdownParser.js.map +1 -1
- package/dist/parse/markdown/HeadingsMarkdownParser.d.ts.map +1 -1
- package/dist/parse/markdown/HeadingsMarkdownParser.js +10 -33
- package/dist/parse/markdown/HeadingsMarkdownParser.js.map +1 -1
- package/dist/parse/markdown/MdxJsxMarkdownParser.d.ts +8 -0
- package/dist/parse/markdown/MdxJsxMarkdownParser.d.ts.map +1 -0
- package/dist/parse/markdown/MdxJsxMarkdownParser.js +61 -0
- package/dist/parse/markdown/MdxJsxMarkdownParser.js.map +1 -0
- package/dist/parse/markdown/MdxMarkdownParser.d.ts +13 -0
- package/dist/parse/markdown/MdxMarkdownParser.d.ts.map +1 -0
- package/dist/parse/markdown/MdxMarkdownParser.js +351 -0
- package/dist/parse/markdown/MdxMarkdownParser.js.map +1 -0
- package/dist/parse/markdown/ShorthandsMarkdownParser.d.ts.map +1 -1
- package/dist/parse/markdown/ShorthandsMarkdownParser.js +57 -2
- package/dist/parse/markdown/ShorthandsMarkdownParser.js.map +1 -1
- package/dist/parse/markdown/parser.d.ts +12 -3
- package/dist/parse/markdown/parser.d.ts.map +1 -1
- package/dist/parse/markdown/parser.js +90 -57
- package/dist/parse/markdown/parser.js.map +1 -1
- package/dist/parse/markdown/plugins.d.ts +26 -0
- package/dist/parse/markdown/plugins.d.ts.map +1 -0
- package/dist/parse/markdown/plugins.js +90 -0
- package/dist/parse/markdown/plugins.js.map +1 -0
- package/dist/parse/markdown/remark-mdx-jsx-only.d.ts +10 -0
- package/dist/parse/markdown/remark-mdx-jsx-only.d.ts.map +1 -0
- package/dist/parse/markdown/remark-mdx-jsx-only.js +26 -0
- package/dist/parse/markdown/remark-mdx-jsx-only.js.map +1 -0
- package/dist/parse/parsers.d.ts +2 -2
- package/dist/parse/parsers.d.ts.map +1 -1
- package/dist/parse/parsers.js +6 -6
- package/dist/parse/parsers.js.map +1 -1
- package/dist/parse/pipeline.d.ts.map +1 -1
- package/dist/parse/pipeline.js +41 -14
- package/dist/parse/pipeline.js.map +1 -1
- package/dist/parse/registry.d.ts +5 -5
- package/dist/parse/registry.d.ts.map +1 -1
- package/dist/parse/registry.js +12 -12
- package/dist/parse/registry.js.map +1 -1
- package/dist/parse/source-mapper.d.ts +42 -0
- package/dist/parse/source-mapper.d.ts.map +1 -0
- package/dist/parse/source-mapper.js +117 -0
- package/dist/parse/source-mapper.js.map +1 -0
- package/dist/parse/types.d.ts +20 -9
- package/dist/parse/types.d.ts.map +1 -1
- package/dist/parse/types.js +2 -13
- package/dist/parse/types.js.map +1 -1
- package/dist/parse/utils/hast-utils.d.ts.map +1 -1
- package/dist/parse/utils/hast-utils.js +69 -29
- package/dist/parse/utils/hast-utils.js.map +1 -1
- package/dist/parse/utils/html-element-utils.d.ts +9 -0
- package/dist/parse/utils/html-element-utils.d.ts.map +1 -0
- package/dist/parse/utils/html-element-utils.js +88 -0
- package/dist/parse/utils/html-element-utils.js.map +1 -0
- package/dist/parse/utils/idl-tokenizer.d.ts +7 -0
- package/dist/parse/utils/idl-tokenizer.d.ts.map +1 -0
- package/dist/parse/utils/idl-tokenizer.js +137 -0
- package/dist/parse/utils/idl-tokenizer.js.map +1 -0
- package/dist/parse/utils/markdown-utils.d.ts +16 -10
- package/dist/parse/utils/markdown-utils.d.ts.map +1 -1
- package/dist/parse/utils/markdown-utils.js +39 -75
- package/dist/parse/utils/markdown-utils.js.map +1 -1
- package/dist/pipeline/index.d.ts.map +1 -1
- package/dist/pipeline/index.js +1 -0
- package/dist/pipeline/index.js.map +1 -1
- package/dist/pipeline/runner.d.ts.map +1 -1
- package/dist/pipeline/runner.js +10 -4
- package/dist/pipeline/runner.js.map +1 -1
- package/dist/pipeline/types.d.ts +2 -0
- package/dist/pipeline/types.d.ts.map +1 -1
- package/dist/postprocess/index.d.ts +3 -1
- package/dist/postprocess/index.d.ts.map +1 -1
- package/dist/postprocess/index.js +9 -3
- package/dist/postprocess/index.js.map +1 -1
- package/dist/postprocess/plugins/bibliography-generator.d.ts.map +1 -1
- package/dist/postprocess/plugins/bibliography-generator.js +20 -3
- package/dist/postprocess/plugins/bibliography-generator.js.map +1 -1
- package/dist/postprocess/plugins/citation-resolve.d.ts.map +1 -1
- package/dist/postprocess/plugins/citation-resolve.js +255 -11
- package/dist/postprocess/plugins/citation-resolve.js.map +1 -1
- package/dist/postprocess/plugins/example-index.d.ts +16 -0
- package/dist/postprocess/plugins/example-index.d.ts.map +1 -0
- package/dist/postprocess/plugins/example-index.js +131 -0
- package/dist/postprocess/plugins/example-index.js.map +1 -0
- package/dist/postprocess/plugins/note-index.d.ts +14 -0
- package/dist/postprocess/plugins/note-index.d.ts.map +1 -0
- package/dist/postprocess/plugins/note-index.js +103 -0
- package/dist/postprocess/plugins/note-index.js.map +1 -0
- package/dist/postprocess/plugins/note-shorthands.d.ts +11 -0
- package/dist/postprocess/plugins/note-shorthands.d.ts.map +1 -0
- package/dist/postprocess/plugins/note-shorthands.js +298 -0
- package/dist/postprocess/plugins/note-shorthands.js.map +1 -0
- package/dist/postprocess/plugins/reference-resolve.d.ts.map +1 -1
- package/dist/postprocess/plugins/reference-resolve.js +46 -1
- package/dist/postprocess/plugins/reference-resolve.js.map +1 -1
- package/dist/postprocess/plugins/statement-distribute.d.ts.map +1 -1
- package/dist/postprocess/plugins/statement-distribute.js +46 -8
- package/dist/postprocess/plugins/statement-distribute.js.map +1 -1
- package/dist/postprocess/plugins/toc.d.ts.map +1 -1
- package/dist/postprocess/plugins/toc.js +20 -16
- package/dist/postprocess/plugins/toc.js.map +1 -1
- package/dist/postprocess/walk-ast.d.ts +5 -1
- package/dist/postprocess/walk-ast.d.ts.map +1 -1
- package/dist/postprocess/walk-ast.js +9 -0
- package/dist/postprocess/walk-ast.js.map +1 -1
- package/dist/preprocess/config/doc-config.d.ts +4 -1
- package/dist/preprocess/config/doc-config.d.ts.map +1 -1
- package/dist/preprocess/config/doc-config.js +6 -3
- package/dist/preprocess/config/doc-config.js.map +1 -1
- package/dist/preprocess/config/normalize.d.ts.map +1 -1
- package/dist/preprocess/config/normalize.js +34 -11
- package/dist/preprocess/config/normalize.js.map +1 -1
- package/dist/preprocess/config/types.d.ts +7 -3
- package/dist/preprocess/config/types.d.ts.map +1 -1
- package/dist/preprocess/include/resolver.d.ts.map +1 -1
- package/dist/preprocess/include/resolver.js +137 -33
- package/dist/preprocess/include/resolver.js.map +1 -1
- package/dist/preprocess/include/scan-html.js +1 -1
- package/dist/preprocess/include/scan-html.js.map +1 -1
- package/dist/preprocess/index.d.ts +1 -1
- package/dist/preprocess/index.d.ts.map +1 -1
- package/dist/preprocess/index.js.map +1 -1
- package/dist/preprocess/pipeline.d.ts.map +1 -1
- package/dist/preprocess/pipeline.js +1 -1
- package/dist/preprocess/pipeline.js.map +1 -1
- package/dist/preprocess/types.d.ts +32 -12
- package/dist/preprocess/types.d.ts.map +1 -1
- package/dist/types/ast.generated.d.ts +69 -13
- package/dist/types/ast.generated.d.ts.map +1 -1
- package/dist/types/ast.generated.js +4 -4
- package/dist/types/ast.generated.js.map +1 -1
- package/dist/workspace/sort.js +1 -1
- package/dist/workspace/sort.js.map +1 -1
- package/package.json +11 -2
- package/schema/spec-ast.schema.json +143 -9
|
@@ -0,0 +1,1156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vocab HTML Parser
|
|
3
|
+
*
|
|
4
|
+
* Handles <spec-vocab> custom elements.
|
|
5
|
+
* Reads pre-loaded sibling metadata from `unit.sideFiles` (populated at preprocess time)
|
|
6
|
+
* to generate normative prose (Tables, Statements) from vocabulary files.
|
|
7
|
+
*
|
|
8
|
+
* This parser is fully isomorphic — it does NOT access the filesystem directly.
|
|
9
|
+
*/
|
|
10
|
+
import * as N3 from 'n3';
|
|
11
|
+
const CONTEXT_FILE_SUFFIX = '.context.jsonld';
|
|
12
|
+
const DEFAULT_CONTEXT_IMPORT_MAX_DEPTH = 20;
|
|
13
|
+
export const VocabHtmlParser = {
|
|
14
|
+
name: 'VocabHtmlParser',
|
|
15
|
+
handles: ['spec-vocab'],
|
|
16
|
+
order: 4,
|
|
17
|
+
handleBlock(element, ctx) {
|
|
18
|
+
const sourcePos = ctx.createSourcePos(element);
|
|
19
|
+
const sideFiles = sourcePos.offset !== undefined ? ctx.sourceMapper.getSideFiles(sourcePos.offset) : undefined;
|
|
20
|
+
if (!sideFiles || Object.keys(sideFiles).length === 0)
|
|
21
|
+
return null;
|
|
22
|
+
const sideFileEntries = toOrderedSideFileEntries(sideFiles);
|
|
23
|
+
const expandedIriAttr = ctx.getAttr(element, 'data-expanded-iri');
|
|
24
|
+
const showExpandedIri = expandedIriAttr === 'data-expanded-iri' || expandedIriAttr === 'true';
|
|
25
|
+
const explicitTarget = getExplicitTargetRequest(element, ctx);
|
|
26
|
+
const contextAttr = ctx.getAttr(element, 'context');
|
|
27
|
+
if (explicitTarget) {
|
|
28
|
+
return resolveTermRequest(explicitTarget, sideFileEntries, ctx, sourcePos);
|
|
29
|
+
}
|
|
30
|
+
if (contextAttr !== undefined) {
|
|
31
|
+
return resolveContextRequest(contextAttr, sideFileEntries, sourcePos.file, sourcePos, sideFiles, showExpandedIri);
|
|
32
|
+
}
|
|
33
|
+
const classFallbackTarget = getClassFallbackTargetRequest(element, ctx);
|
|
34
|
+
if (classFallbackTarget) {
|
|
35
|
+
return resolveTermRequest(classFallbackTarget, sideFileEntries, ctx, sourcePos);
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
function resolveTermRequest(request, sideFiles, ctx, sourcePos) {
|
|
41
|
+
const term = request.value;
|
|
42
|
+
const ttlFiles = sideFiles.filter((entry) => entry.fileName.endsWith('.ttl'));
|
|
43
|
+
if (ttlFiles.length > 0) {
|
|
44
|
+
const bundle = buildMergedTtlBundle(ttlFiles);
|
|
45
|
+
if (bundle) {
|
|
46
|
+
const termIri = resolveTermIri(term, bundle.prefixes, bundle.store);
|
|
47
|
+
if (termIri) {
|
|
48
|
+
if (request.kind === 'class') {
|
|
49
|
+
return generateClassProse(termIri, bundle.store, ctx, sourcePos);
|
|
50
|
+
}
|
|
51
|
+
return generatePropertyProse(termIri, bundle.store, sourcePos);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const jsonLdFiles = sideFiles.filter((entry) => entry.fileName.endsWith('.jsonld') && !entry.fileName.endsWith(CONTEXT_FILE_SUFFIX));
|
|
56
|
+
for (const jsonLdFile of jsonLdFiles) {
|
|
57
|
+
const result = parseJsonLd(jsonLdFile.content, request, sourcePos);
|
|
58
|
+
if (result)
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
function resolveContextRequest(contextAttr, sideFiles, unitFile, sourcePos, rawSideFiles, showExpandedIri) {
|
|
64
|
+
const normalizedContextAttr = contextAttr.trim();
|
|
65
|
+
const isDefault = normalizedContextAttr === 'context' || normalizedContextAttr === '';
|
|
66
|
+
const contextFiles = sideFiles.filter((entry) => entry.fileName.endsWith(CONTEXT_FILE_SUFFIX));
|
|
67
|
+
if (contextFiles.length === 0)
|
|
68
|
+
return null;
|
|
69
|
+
if (isDefault) {
|
|
70
|
+
const folderName = getParentFolderName(unitFile);
|
|
71
|
+
if (folderName) {
|
|
72
|
+
const defaultContextFile = `${folderName}${CONTEXT_FILE_SUFFIX}`;
|
|
73
|
+
const matched = contextFiles.find((entry) => entry.fileName === defaultContextFile);
|
|
74
|
+
if (matched) {
|
|
75
|
+
return parseContextJsonLd(matched.content, folderName, sourcePos, rawSideFiles, matched.normalizedPath, showExpandedIri);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (contextFiles.length === 1) {
|
|
79
|
+
const onlyContext = contextFiles[0];
|
|
80
|
+
const fallbackName = stripContextFileSuffix(onlyContext.fileName) || folderName || 'default';
|
|
81
|
+
return parseContextJsonLd(onlyContext.content, fallbackName, sourcePos, rawSideFiles, onlyContext.normalizedPath, showExpandedIri);
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
const contextFileName = normalizedContextAttr.endsWith(CONTEXT_FILE_SUFFIX)
|
|
86
|
+
? normalizedContextAttr
|
|
87
|
+
: `${normalizedContextAttr}${CONTEXT_FILE_SUFFIX}`;
|
|
88
|
+
const matched = contextFiles.find((entry) => entry.fileName === contextFileName);
|
|
89
|
+
if (!matched)
|
|
90
|
+
return null;
|
|
91
|
+
const contextName = stripContextFileSuffix(matched.fileName) || normalizedContextAttr;
|
|
92
|
+
return parseContextJsonLd(matched.content, contextName, sourcePos, rawSideFiles, matched.normalizedPath, showExpandedIri);
|
|
93
|
+
}
|
|
94
|
+
function getExplicitTargetRequest(element, ctx) {
|
|
95
|
+
const explicitTerm = normalizeAttrValue(ctx.getAttr(element, 'term'));
|
|
96
|
+
if (explicitTerm) {
|
|
97
|
+
return { kind: 'class', value: explicitTerm, source: 'term' };
|
|
98
|
+
}
|
|
99
|
+
const classIri = normalizeAttrValue(ctx.getAttr(element, 'classIri') ?? ctx.getAttr(element, 'class-iri') ?? ctx.getAttr(element, 'classiri'));
|
|
100
|
+
if (classIri) {
|
|
101
|
+
return { kind: 'class', value: classIri, source: 'classIri' };
|
|
102
|
+
}
|
|
103
|
+
const propertyValue = ctx.getAttr(element, 'property');
|
|
104
|
+
const normalizedProperty = normalizeAttrValue(propertyValue);
|
|
105
|
+
if (normalizedProperty) {
|
|
106
|
+
return { kind: 'property', value: normalizedProperty, source: 'property' };
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
function getClassFallbackTargetRequest(element, ctx) {
|
|
111
|
+
const classValue = ctx.getAttr(element, 'class') ?? ctx.getAttr(element, 'className');
|
|
112
|
+
const classTerm = getFallbackClassTerm(classValue);
|
|
113
|
+
if (!classTerm)
|
|
114
|
+
return null;
|
|
115
|
+
return { kind: 'class', value: classTerm, source: 'class' };
|
|
116
|
+
}
|
|
117
|
+
function normalizeAttrValue(rawValue) {
|
|
118
|
+
if (!rawValue)
|
|
119
|
+
return undefined;
|
|
120
|
+
const normalized = rawValue.trim();
|
|
121
|
+
return normalized.length > 0 ? normalized : undefined;
|
|
122
|
+
}
|
|
123
|
+
function getFallbackClassTerm(rawValue) {
|
|
124
|
+
if (!rawValue)
|
|
125
|
+
return undefined;
|
|
126
|
+
const tokens = rawValue
|
|
127
|
+
.split(/\s+/)
|
|
128
|
+
.map((token) => token.trim())
|
|
129
|
+
.filter((token) => token.length > 0);
|
|
130
|
+
if (tokens.length === 0)
|
|
131
|
+
return undefined;
|
|
132
|
+
const likelyTermToken = tokens.find(isLikelyTermToken);
|
|
133
|
+
if (likelyTermToken)
|
|
134
|
+
return likelyTermToken;
|
|
135
|
+
return tokens.length === 1 ? tokens[0] : undefined;
|
|
136
|
+
}
|
|
137
|
+
function isLikelyTermToken(token) {
|
|
138
|
+
return token.includes(':') || token.startsWith('http://') || token.startsWith('https://');
|
|
139
|
+
}
|
|
140
|
+
function toOrderedSideFileEntries(sideFiles) {
|
|
141
|
+
return Object.entries(sideFiles)
|
|
142
|
+
.map(([path, content]) => ({
|
|
143
|
+
path,
|
|
144
|
+
normalizedPath: normalizePath(path),
|
|
145
|
+
fileName: getFileName(path),
|
|
146
|
+
content,
|
|
147
|
+
}))
|
|
148
|
+
.sort((a, b) => a.normalizedPath.localeCompare(b.normalizedPath));
|
|
149
|
+
}
|
|
150
|
+
function normalizePath(path) {
|
|
151
|
+
const slashNormalized = path.replace(/\\/g, '/').replace(/\/+/g, '/');
|
|
152
|
+
if (slashNormalized.length > 1 && slashNormalized.endsWith('/')) {
|
|
153
|
+
return slashNormalized.slice(0, -1);
|
|
154
|
+
}
|
|
155
|
+
return slashNormalized;
|
|
156
|
+
}
|
|
157
|
+
function getFileName(path) {
|
|
158
|
+
const normalized = normalizePath(path);
|
|
159
|
+
const lastSlash = normalized.lastIndexOf('/');
|
|
160
|
+
if (lastSlash === -1)
|
|
161
|
+
return normalized;
|
|
162
|
+
return normalized.slice(lastSlash + 1);
|
|
163
|
+
}
|
|
164
|
+
function getParentFolderName(path) {
|
|
165
|
+
if (!path)
|
|
166
|
+
return '';
|
|
167
|
+
const normalized = normalizePath(path);
|
|
168
|
+
const lastSlash = normalized.lastIndexOf('/');
|
|
169
|
+
if (lastSlash <= 0)
|
|
170
|
+
return '';
|
|
171
|
+
const parentPath = normalized.slice(0, lastSlash);
|
|
172
|
+
const parentLastSlash = parentPath.lastIndexOf('/');
|
|
173
|
+
if (parentLastSlash === -1)
|
|
174
|
+
return parentPath;
|
|
175
|
+
return parentPath.slice(parentLastSlash + 1);
|
|
176
|
+
}
|
|
177
|
+
function stripContextFileSuffix(fileName) {
|
|
178
|
+
if (fileName.endsWith(CONTEXT_FILE_SUFFIX)) {
|
|
179
|
+
return fileName.slice(0, -CONTEXT_FILE_SUFFIX.length);
|
|
180
|
+
}
|
|
181
|
+
return fileName;
|
|
182
|
+
}
|
|
183
|
+
// ============================================================================
|
|
184
|
+
// TTL Parsing
|
|
185
|
+
// ============================================================================
|
|
186
|
+
function buildMergedTtlBundle(ttlFiles) {
|
|
187
|
+
const store = new N3.Store();
|
|
188
|
+
const prefixes = {};
|
|
189
|
+
let parsedAtLeastOneFile = false;
|
|
190
|
+
for (const ttlFile of ttlFiles) {
|
|
191
|
+
try {
|
|
192
|
+
const parser = new N3.Parser();
|
|
193
|
+
const quads = parser.parse(ttlFile.content, null, (prefix, ns) => {
|
|
194
|
+
if (!prefix || !ns)
|
|
195
|
+
return;
|
|
196
|
+
const namespace = typeof ns === 'string' ? ns : ns.value;
|
|
197
|
+
if (namespace) {
|
|
198
|
+
prefixes[prefix] = namespace;
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
store.addQuads(quads);
|
|
202
|
+
parsedAtLeastOneFile = true;
|
|
203
|
+
}
|
|
204
|
+
catch (e) {
|
|
205
|
+
console.warn(`VocabHtmlParser: Failed to parse TTL file ${ttlFile.path}:`, e);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (!parsedAtLeastOneFile)
|
|
209
|
+
return null;
|
|
210
|
+
return { store, prefixes };
|
|
211
|
+
}
|
|
212
|
+
function resolveTermIri(term, prefixes, store) {
|
|
213
|
+
const namedNode = N3.DataFactory.namedNode;
|
|
214
|
+
const hasTermInStore = (iri) => (store.countQuads(namedNode(iri), null, null, null) > 0 ||
|
|
215
|
+
store.countQuads(null, null, namedNode(iri), null) > 0);
|
|
216
|
+
// Try prefix expansion first (e.g. "ujg:Node")
|
|
217
|
+
if (term.includes(':')) {
|
|
218
|
+
const colonIdx = term.indexOf(':');
|
|
219
|
+
const prefix = term.slice(0, colonIdx);
|
|
220
|
+
const local = term.slice(colonIdx + 1);
|
|
221
|
+
const ns = prefixes[prefix];
|
|
222
|
+
if (ns) {
|
|
223
|
+
const expanded = ns + local;
|
|
224
|
+
if (hasTermInStore(expanded))
|
|
225
|
+
return expanded;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// Fallback: match local name in store subjects
|
|
229
|
+
const localName = term.includes(':') ? term.split(':').pop() : term;
|
|
230
|
+
const subject = store.getSubjects(null, null, null).find((s) => s.termType === 'NamedNode' &&
|
|
231
|
+
(s.value.endsWith('#' + localName) || s.value.endsWith('/' + localName)));
|
|
232
|
+
if (subject?.value)
|
|
233
|
+
return subject.value;
|
|
234
|
+
const object = store.getObjects(null, null, null).find((o) => o.termType === 'NamedNode' &&
|
|
235
|
+
(o.value.endsWith('#' + localName) || o.value.endsWith('/' + localName)));
|
|
236
|
+
return object?.termType === 'NamedNode' ? object.value : undefined;
|
|
237
|
+
}
|
|
238
|
+
// ============================================================================
|
|
239
|
+
// JSON-LD Parsing
|
|
240
|
+
// ============================================================================
|
|
241
|
+
function parseJsonLd(content, request, sourcePos) {
|
|
242
|
+
try {
|
|
243
|
+
const parsed = JSON.parse(content);
|
|
244
|
+
const graph = Array.isArray(parsed['@graph'])
|
|
245
|
+
? parsed['@graph']
|
|
246
|
+
: Array.isArray(parsed)
|
|
247
|
+
? parsed
|
|
248
|
+
: [parsed];
|
|
249
|
+
const localName = request.value.includes(':') ? request.value.split(':').pop() : request.value;
|
|
250
|
+
const termNode = graph.find((node) => typeof node['@id'] === 'string' &&
|
|
251
|
+
(node['@id'] === request.value ||
|
|
252
|
+
node['@id'].endsWith('#' + localName) ||
|
|
253
|
+
node['@id'].endsWith('/' + localName)));
|
|
254
|
+
if (termNode) {
|
|
255
|
+
if (request.kind === 'class') {
|
|
256
|
+
return generateClassProseFromJson(termNode, sourcePos);
|
|
257
|
+
}
|
|
258
|
+
return generatePropertyProseFromJson(termNode, sourcePos);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
catch (e) {
|
|
262
|
+
console.warn(`VocabHtmlParser: Failed to parse JSON-LD:`, e);
|
|
263
|
+
}
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
function parseContextJsonLd(content, contextName, sourcePos, sideFiles, contextPath, showExpandedIri = false) {
|
|
267
|
+
try {
|
|
268
|
+
const parsed = JSON.parse(content);
|
|
269
|
+
const doc = parsed;
|
|
270
|
+
const ctxNode = doc['@context'] || doc;
|
|
271
|
+
const flattenedContext = normalizeContext(ctxNode, sideFiles || {}, {
|
|
272
|
+
maxImportDepth: DEFAULT_CONTEXT_IMPORT_MAX_DEPTH,
|
|
273
|
+
contextPath,
|
|
274
|
+
});
|
|
275
|
+
const model = extractContextModel(flattenedContext, contextName);
|
|
276
|
+
return generateContextProse(model, sourcePos, showExpandedIri);
|
|
277
|
+
}
|
|
278
|
+
catch (e) {
|
|
279
|
+
console.warn(`VocabHtmlParser: Failed to parse JSON-LD Context:`, e);
|
|
280
|
+
}
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
function normalizeContext(ctxNode, sideFiles, options) {
|
|
284
|
+
const flat = {};
|
|
285
|
+
const orderedSideFiles = toOrderedSideFileEntries(sideFiles);
|
|
286
|
+
const visitedImports = new Set();
|
|
287
|
+
const maxImportDepth = options?.maxImportDepth ?? DEFAULT_CONTEXT_IMPORT_MAX_DEPTH;
|
|
288
|
+
const processImport = (importRef, depth, importerPath) => {
|
|
289
|
+
if (depth > maxImportDepth) {
|
|
290
|
+
console.warn(`VocabHtmlParser: Max JSON-LD context import depth (${maxImportDepth}) exceeded for ${importRef}`);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const imported = findImportedContext(importRef, orderedSideFiles, importerPath ?? options?.contextPath);
|
|
294
|
+
if (!imported)
|
|
295
|
+
return;
|
|
296
|
+
if (visitedImports.has(imported.id)) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
visitedImports.add(imported.id);
|
|
300
|
+
processNode(imported.contextNode, depth + 1, imported.path);
|
|
301
|
+
};
|
|
302
|
+
const processNode = (node, depth, currentContextPath) => {
|
|
303
|
+
if (depth > maxImportDepth) {
|
|
304
|
+
console.warn(`VocabHtmlParser: Max JSON-LD context nesting depth (${maxImportDepth}) exceeded`);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (typeof node === 'string') {
|
|
308
|
+
// It could be an @import string instead of an object
|
|
309
|
+
processImport(node, depth, currentContextPath);
|
|
310
|
+
}
|
|
311
|
+
else if (Array.isArray(node)) {
|
|
312
|
+
for (const item of node) {
|
|
313
|
+
processNode(item, depth, currentContextPath);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
else if (typeof node === 'object' && node !== null) {
|
|
317
|
+
const obj = node;
|
|
318
|
+
// Handle @import
|
|
319
|
+
if (typeof obj['@import'] === 'string') {
|
|
320
|
+
processImport(obj['@import'], depth, currentContextPath);
|
|
321
|
+
}
|
|
322
|
+
// Merge keys (overriding earlier ones, matching JSON-LD behavior)
|
|
323
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
324
|
+
if (k !== '@import') {
|
|
325
|
+
flat[k] = v;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
processNode(ctxNode, 0, options?.contextPath);
|
|
331
|
+
return flat;
|
|
332
|
+
}
|
|
333
|
+
function findImportedContext(reference, sideFiles, importerPath) {
|
|
334
|
+
const sanitizedReference = sanitizeImportReference(reference);
|
|
335
|
+
if (!sanitizedReference)
|
|
336
|
+
return null;
|
|
337
|
+
const candidates = [];
|
|
338
|
+
const seenCandidates = new Set();
|
|
339
|
+
const addCandidate = (candidate) => {
|
|
340
|
+
if (!candidate)
|
|
341
|
+
return;
|
|
342
|
+
if (seenCandidates.has(candidate.normalizedPath))
|
|
343
|
+
return;
|
|
344
|
+
seenCandidates.add(candidate.normalizedPath);
|
|
345
|
+
candidates.push(candidate);
|
|
346
|
+
};
|
|
347
|
+
const normalizedReference = normalizePathSegments(sanitizedReference);
|
|
348
|
+
if (isAbsolutePath(normalizedReference)) {
|
|
349
|
+
addCandidate(sideFiles.find((entry) => entry.normalizedPath === normalizedReference));
|
|
350
|
+
}
|
|
351
|
+
else if (importerPath) {
|
|
352
|
+
const resolved = resolvePathFromImporter(importerPath, normalizedReference);
|
|
353
|
+
addCandidate(sideFiles.find((entry) => entry.normalizedPath === resolved));
|
|
354
|
+
}
|
|
355
|
+
if (normalizedReference.includes('/')) {
|
|
356
|
+
const suffixMatches = sideFiles.filter((entry) => pathEndsWith(entry.normalizedPath, normalizedReference));
|
|
357
|
+
for (const candidate of rankCandidatesByImporter(suffixMatches, importerPath)) {
|
|
358
|
+
addCandidate(candidate);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
const targetFilename = getImportTargetFileName(reference);
|
|
362
|
+
const filenameMatches = sideFiles.filter((entry) => entry.fileName === targetFilename);
|
|
363
|
+
for (const candidate of rankCandidatesByImporter(filenameMatches, importerPath)) {
|
|
364
|
+
addCandidate(candidate);
|
|
365
|
+
}
|
|
366
|
+
for (const candidate of candidates) {
|
|
367
|
+
try {
|
|
368
|
+
const parsed = JSON.parse(candidate.content);
|
|
369
|
+
return {
|
|
370
|
+
id: candidate.normalizedPath,
|
|
371
|
+
path: candidate.normalizedPath,
|
|
372
|
+
contextNode: parsed['@context'] || parsed,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
catch (e) {
|
|
376
|
+
console.warn(`VocabHtmlParser: Failed to parse imported JSON-LD context from ${candidate.path}:`, e);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
function getImportTargetFileName(reference) {
|
|
382
|
+
const sanitizedReference = sanitizeImportReference(reference);
|
|
383
|
+
if (!sanitizedReference)
|
|
384
|
+
return '';
|
|
385
|
+
return getFileName(sanitizedReference);
|
|
386
|
+
}
|
|
387
|
+
function sanitizeImportReference(reference) {
|
|
388
|
+
return reference.split('#')[0].split('?')[0].trim();
|
|
389
|
+
}
|
|
390
|
+
function rankCandidatesByImporter(candidates, importerPath) {
|
|
391
|
+
if (candidates.length <= 1 || !importerPath)
|
|
392
|
+
return candidates;
|
|
393
|
+
const importerDir = getDirectoryPath(importerPath);
|
|
394
|
+
return [...candidates].sort((a, b) => {
|
|
395
|
+
const distanceDelta = getPathDistance(importerDir, getDirectoryPath(a.normalizedPath))
|
|
396
|
+
- getPathDistance(importerDir, getDirectoryPath(b.normalizedPath));
|
|
397
|
+
if (distanceDelta !== 0)
|
|
398
|
+
return distanceDelta;
|
|
399
|
+
return a.normalizedPath.localeCompare(b.normalizedPath);
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
function getPathDistance(fromPath, toPath) {
|
|
403
|
+
const fromSegments = getPathSegments(fromPath);
|
|
404
|
+
const toSegments = getPathSegments(toPath);
|
|
405
|
+
const commonDepth = getCommonPrefixDepth(fromSegments, toSegments);
|
|
406
|
+
const fromRemaining = fromSegments.length - commonDepth;
|
|
407
|
+
const toRemaining = toSegments.length - commonDepth;
|
|
408
|
+
return fromRemaining + toRemaining;
|
|
409
|
+
}
|
|
410
|
+
function getCommonPrefixDepth(a, b) {
|
|
411
|
+
const limit = Math.min(a.length, b.length);
|
|
412
|
+
let index = 0;
|
|
413
|
+
while (index < limit && a[index] === b[index]) {
|
|
414
|
+
index++;
|
|
415
|
+
}
|
|
416
|
+
return index;
|
|
417
|
+
}
|
|
418
|
+
function getPathSegments(path) {
|
|
419
|
+
const normalized = normalizePathSegments(path);
|
|
420
|
+
const withoutDrive = normalized.replace(/^[A-Za-z]:\//, '');
|
|
421
|
+
const withoutRoot = withoutDrive.startsWith('/') ? withoutDrive.slice(1) : withoutDrive;
|
|
422
|
+
if (!withoutRoot)
|
|
423
|
+
return [];
|
|
424
|
+
return withoutRoot.split('/').filter((segment) => segment.length > 0);
|
|
425
|
+
}
|
|
426
|
+
function getDirectoryPath(path) {
|
|
427
|
+
const normalized = normalizePath(path);
|
|
428
|
+
const lastSlash = normalized.lastIndexOf('/');
|
|
429
|
+
if (lastSlash === -1)
|
|
430
|
+
return '';
|
|
431
|
+
if (lastSlash === 0)
|
|
432
|
+
return '/';
|
|
433
|
+
return normalized.slice(0, lastSlash);
|
|
434
|
+
}
|
|
435
|
+
function isAbsolutePath(path) {
|
|
436
|
+
return path.startsWith('/') || /^[A-Za-z]:\//.test(path);
|
|
437
|
+
}
|
|
438
|
+
function resolvePathFromImporter(importerPath, referencePath) {
|
|
439
|
+
if (isAbsolutePath(referencePath)) {
|
|
440
|
+
return normalizePathSegments(referencePath);
|
|
441
|
+
}
|
|
442
|
+
const importerDir = getDirectoryPath(importerPath);
|
|
443
|
+
const joined = importerDir ? `${importerDir}/${referencePath}` : referencePath;
|
|
444
|
+
return normalizePathSegments(joined);
|
|
445
|
+
}
|
|
446
|
+
function pathEndsWith(candidatePath, referencePath) {
|
|
447
|
+
const normalizedCandidate = normalizePathSegments(candidatePath);
|
|
448
|
+
const normalizedReference = normalizePathSegments(referencePath);
|
|
449
|
+
if (normalizedCandidate === normalizedReference)
|
|
450
|
+
return true;
|
|
451
|
+
return normalizedCandidate.endsWith(`/${normalizedReference}`);
|
|
452
|
+
}
|
|
453
|
+
function normalizePathSegments(path) {
|
|
454
|
+
const normalized = normalizePath(path);
|
|
455
|
+
if (!normalized)
|
|
456
|
+
return normalized;
|
|
457
|
+
const hasDrivePrefix = /^[A-Za-z]:\//.test(normalized);
|
|
458
|
+
const drivePrefix = hasDrivePrefix ? normalized.slice(0, 2) : '';
|
|
459
|
+
const absoluteWithoutDrive = hasDrivePrefix ? normalized.slice(2).startsWith('/') : normalized.startsWith('/');
|
|
460
|
+
const pathWithoutPrefix = hasDrivePrefix ? normalized.slice(2) : normalized;
|
|
461
|
+
const rawSegments = pathWithoutPrefix.split('/');
|
|
462
|
+
const resolved = [];
|
|
463
|
+
for (const segment of rawSegments) {
|
|
464
|
+
if (!segment || segment === '.')
|
|
465
|
+
continue;
|
|
466
|
+
if (segment === '..') {
|
|
467
|
+
if (resolved.length > 0 && resolved[resolved.length - 1] !== '..') {
|
|
468
|
+
resolved.pop();
|
|
469
|
+
}
|
|
470
|
+
else if (!absoluteWithoutDrive) {
|
|
471
|
+
resolved.push('..');
|
|
472
|
+
}
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
resolved.push(segment);
|
|
476
|
+
}
|
|
477
|
+
const joined = resolved.join('/');
|
|
478
|
+
if (drivePrefix) {
|
|
479
|
+
return joined ? `${drivePrefix}/${joined}` : `${drivePrefix}/`;
|
|
480
|
+
}
|
|
481
|
+
if (absoluteWithoutDrive) {
|
|
482
|
+
return joined ? `/${joined}` : '/';
|
|
483
|
+
}
|
|
484
|
+
return joined;
|
|
485
|
+
}
|
|
486
|
+
function extractContextModel(ctxNode, contextName) {
|
|
487
|
+
const model = {
|
|
488
|
+
name: contextName,
|
|
489
|
+
prefixes: {},
|
|
490
|
+
terms: []
|
|
491
|
+
};
|
|
492
|
+
if (typeof ctxNode['@vocab'] === 'string') {
|
|
493
|
+
model.vocab = ctxNode['@vocab'];
|
|
494
|
+
}
|
|
495
|
+
for (const [key, value] of Object.entries(ctxNode)) {
|
|
496
|
+
if (key.startsWith('@'))
|
|
497
|
+
continue; // Skip keywords like @vocab, @version
|
|
498
|
+
if (typeof value === 'string') {
|
|
499
|
+
if (value === '@nest') { // e.g. "meta": "@nest"
|
|
500
|
+
model.terms.push({ term: key, isNest: true });
|
|
501
|
+
}
|
|
502
|
+
else if (value.startsWith('@')) {
|
|
503
|
+
model.terms.push({ term: key, isAlias: true, targetKeyword: value });
|
|
504
|
+
}
|
|
505
|
+
else {
|
|
506
|
+
const isPrefix = isContextPrefixDeclaration(key, value);
|
|
507
|
+
if (isPrefix) {
|
|
508
|
+
model.prefixes[key] = value;
|
|
509
|
+
}
|
|
510
|
+
model.terms.push({ term: key, iri: value, isPrefix });
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
else if (typeof value === 'object' && value !== null) {
|
|
514
|
+
const termDef = value;
|
|
515
|
+
if (typeof termDef['@id'] === 'string') {
|
|
516
|
+
const isPrefix = termDef['@prefix'] === true && isAbsoluteIri(termDef['@id']);
|
|
517
|
+
if (isPrefix) {
|
|
518
|
+
model.prefixes[key] = termDef['@id'];
|
|
519
|
+
}
|
|
520
|
+
const rule = { term: key, iri: termDef['@id'], isPrefix };
|
|
521
|
+
if (typeof termDef['@type'] === 'string') {
|
|
522
|
+
rule.typeCoercion = termDef['@type'];
|
|
523
|
+
}
|
|
524
|
+
if (typeof termDef['@container'] === 'string') {
|
|
525
|
+
rule.container = termDef['@container'];
|
|
526
|
+
}
|
|
527
|
+
model.terms.push(rule);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return model;
|
|
532
|
+
}
|
|
533
|
+
// ============================================================================
|
|
534
|
+
// Prose Generation
|
|
535
|
+
// ============================================================================
|
|
536
|
+
const RDFS = {
|
|
537
|
+
label: 'http://www.w3.org/2000/01/rdf-schema#label',
|
|
538
|
+
comment: 'http://www.w3.org/2000/01/rdf-schema#comment',
|
|
539
|
+
domain: 'http://www.w3.org/2000/01/rdf-schema#domain',
|
|
540
|
+
range: 'http://www.w3.org/2000/01/rdf-schema#range',
|
|
541
|
+
};
|
|
542
|
+
const SHACL = {
|
|
543
|
+
targetClass: 'http://www.w3.org/ns/shacl#targetClass',
|
|
544
|
+
property: 'http://www.w3.org/ns/shacl#property',
|
|
545
|
+
path: 'http://www.w3.org/ns/shacl#path',
|
|
546
|
+
minCount: 'http://www.w3.org/ns/shacl#minCount',
|
|
547
|
+
maxCount: 'http://www.w3.org/ns/shacl#maxCount',
|
|
548
|
+
datatype: 'http://www.w3.org/ns/shacl#datatype',
|
|
549
|
+
class: 'http://www.w3.org/ns/shacl#class',
|
|
550
|
+
node: 'http://www.w3.org/ns/shacl#node',
|
|
551
|
+
nodeKind: 'http://www.w3.org/ns/shacl#nodeKind',
|
|
552
|
+
};
|
|
553
|
+
function generateClassProse(iri, store, ctx, sourcePos) {
|
|
554
|
+
void ctx; // ctx reserved for future use (e.g. xref linking)
|
|
555
|
+
const blocks = [];
|
|
556
|
+
const comment = getFirstLiteralObjectValue(store, iri, RDFS.comment);
|
|
557
|
+
if (comment) {
|
|
558
|
+
blocks.push({
|
|
559
|
+
type: 'paragraph',
|
|
560
|
+
children: [{ type: 'text', value: comment }],
|
|
561
|
+
sourcePos
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
// blocks.push({
|
|
565
|
+
// type: 'paragraph',
|
|
566
|
+
// children: [
|
|
567
|
+
// { type: 'text', value: 'It ' },
|
|
568
|
+
// { type: 'requirement', keyword: 'MUST' },
|
|
569
|
+
// { type: 'text', value: ' satisfy the following schema:' }
|
|
570
|
+
// ],
|
|
571
|
+
// sourcePos
|
|
572
|
+
// });
|
|
573
|
+
const propertyModels = buildClassPropertyModels(iri, store);
|
|
574
|
+
if (propertyModels.length > 0) {
|
|
575
|
+
const rows = [
|
|
576
|
+
{
|
|
577
|
+
type: 'tableRow',
|
|
578
|
+
children: [
|
|
579
|
+
createHeaderCell('Field'),
|
|
580
|
+
createHeaderCell('Requirement'),
|
|
581
|
+
createHeaderCell('Description'),
|
|
582
|
+
createHeaderCell('Value Type')
|
|
583
|
+
]
|
|
584
|
+
}
|
|
585
|
+
];
|
|
586
|
+
for (const property of propertyModels) {
|
|
587
|
+
rows.push({
|
|
588
|
+
type: 'tableRow',
|
|
589
|
+
children: [
|
|
590
|
+
createCell(property.name, true),
|
|
591
|
+
createCell(formatRequirement(property.cardinality)),
|
|
592
|
+
createCell(property.comment || property.label || ''),
|
|
593
|
+
createCell(property.valueType, true)
|
|
594
|
+
]
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
blocks.push({
|
|
598
|
+
type: 'table',
|
|
599
|
+
children: rows,
|
|
600
|
+
id: getLocalName(iri),
|
|
601
|
+
sourcePos
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
return blocks;
|
|
605
|
+
}
|
|
606
|
+
function generatePropertyProse(propertyIri, store, sourcePos) {
|
|
607
|
+
const blocks = [];
|
|
608
|
+
const propertyName = getLocalName(propertyIri);
|
|
609
|
+
const comment = getFirstLiteralObjectValue(store, propertyIri, RDFS.comment);
|
|
610
|
+
const label = getFirstLiteralObjectValue(store, propertyIri, RDFS.label);
|
|
611
|
+
const domains = getNamedNodeObjectValues(store, propertyIri, RDFS.domain).map((iri) => getLocalName(iri));
|
|
612
|
+
const ranges = getNamedNodeObjectValues(store, propertyIri, RDFS.range).map((iri) => getLocalName(iri));
|
|
613
|
+
const targetClasses = getShaclTargetClassesForProperty(propertyIri, store).map((iri) => getLocalName(iri));
|
|
614
|
+
if (comment) {
|
|
615
|
+
blocks.push({
|
|
616
|
+
type: 'paragraph',
|
|
617
|
+
children: [{ type: 'text', value: comment }],
|
|
618
|
+
sourcePos
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
blocks.push({
|
|
622
|
+
type: 'paragraph',
|
|
623
|
+
children: [
|
|
624
|
+
{ type: 'text', value: 'The ' },
|
|
625
|
+
{ type: 'inlineCode', value: propertyName },
|
|
626
|
+
{ type: 'text', value: ' property ' },
|
|
627
|
+
{ type: 'requirement', keyword: 'MUST' },
|
|
628
|
+
{ type: 'text', value: ' satisfy the following definition metadata:' }
|
|
629
|
+
],
|
|
630
|
+
sourcePos
|
|
631
|
+
});
|
|
632
|
+
const rows = [
|
|
633
|
+
{
|
|
634
|
+
type: 'tableRow',
|
|
635
|
+
children: [
|
|
636
|
+
createHeaderCell('Constraint'),
|
|
637
|
+
createHeaderCell('Value')
|
|
638
|
+
]
|
|
639
|
+
},
|
|
640
|
+
{
|
|
641
|
+
type: 'tableRow',
|
|
642
|
+
children: [
|
|
643
|
+
createCell('IRI', true),
|
|
644
|
+
createCell(propertyIri, true)
|
|
645
|
+
]
|
|
646
|
+
}
|
|
647
|
+
];
|
|
648
|
+
if (label) {
|
|
649
|
+
rows.push({
|
|
650
|
+
type: 'tableRow',
|
|
651
|
+
children: [
|
|
652
|
+
createCell('Label', true),
|
|
653
|
+
createCell(label)
|
|
654
|
+
]
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
if (domains.length > 0) {
|
|
658
|
+
rows.push({
|
|
659
|
+
type: 'tableRow',
|
|
660
|
+
children: [
|
|
661
|
+
createCell('Domain', true),
|
|
662
|
+
createCell(domains.join(', '), true)
|
|
663
|
+
]
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
if (ranges.length > 0) {
|
|
667
|
+
rows.push({
|
|
668
|
+
type: 'tableRow',
|
|
669
|
+
children: [
|
|
670
|
+
createCell('Range', true),
|
|
671
|
+
createCell(ranges.join(', '), true)
|
|
672
|
+
]
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
if (targetClasses.length > 0) {
|
|
676
|
+
rows.push({
|
|
677
|
+
type: 'tableRow',
|
|
678
|
+
children: [
|
|
679
|
+
createCell('SHACL Target Class', true),
|
|
680
|
+
createCell(targetClasses.join(', '), true)
|
|
681
|
+
]
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
blocks.push({
|
|
685
|
+
type: 'table',
|
|
686
|
+
children: rows,
|
|
687
|
+
id: propertyName,
|
|
688
|
+
sourcePos
|
|
689
|
+
});
|
|
690
|
+
return blocks;
|
|
691
|
+
}
|
|
692
|
+
function buildClassPropertyModels(classIri, store) {
|
|
693
|
+
const propertyNodes = discoverClassProperties(classIri, store);
|
|
694
|
+
return propertyNodes.map((propertyIri) => {
|
|
695
|
+
const shacl = collectShaclConstraintsForClassProperty(classIri, propertyIri, store);
|
|
696
|
+
const rdfsRanges = getNamedNodeObjectValues(store, propertyIri, RDFS.range).map((iri) => getLocalName(iri));
|
|
697
|
+
return {
|
|
698
|
+
iri: propertyIri,
|
|
699
|
+
name: getLocalName(propertyIri),
|
|
700
|
+
label: getFirstLiteralObjectValue(store, propertyIri, RDFS.label),
|
|
701
|
+
comment: getFirstLiteralObjectValue(store, propertyIri, RDFS.comment),
|
|
702
|
+
valueType: resolveValueType(shacl.valueTypes, rdfsRanges),
|
|
703
|
+
cardinality: {
|
|
704
|
+
cardinality: shacl.cardinality,
|
|
705
|
+
contributingShapeCount: shacl.contributingShapeCount,
|
|
706
|
+
hasConflict: hasCardinalityConflict(shacl.cardinality),
|
|
707
|
+
},
|
|
708
|
+
};
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
function discoverClassProperties(classIri, store) {
|
|
712
|
+
const propertySet = new Set();
|
|
713
|
+
const namedNode = N3.DataFactory.namedNode;
|
|
714
|
+
const domainQuads = store.getQuads(null, namedNode(RDFS.domain), namedNode(classIri), null);
|
|
715
|
+
for (const quad of domainQuads) {
|
|
716
|
+
if (quad.subject.termType === 'NamedNode') {
|
|
717
|
+
propertySet.add(quad.subject.value);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
const nodeShapes = store.getSubjects(namedNode(SHACL.targetClass), namedNode(classIri), null);
|
|
721
|
+
for (const nodeShape of nodeShapes) {
|
|
722
|
+
const propertyShapes = store.getObjects(nodeShape, namedNode(SHACL.property), null);
|
|
723
|
+
for (const propertyShape of propertyShapes) {
|
|
724
|
+
const pathNode = store.getObjects(propertyShape, namedNode(SHACL.path), null)[0];
|
|
725
|
+
if (pathNode?.termType === 'NamedNode') {
|
|
726
|
+
propertySet.add(pathNode.value);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
return Array.from(propertySet);
|
|
731
|
+
}
|
|
732
|
+
function collectShaclConstraintsForClassProperty(classIri, propertyIri, store) {
|
|
733
|
+
const namedNode = N3.DataFactory.namedNode;
|
|
734
|
+
const nodeShapes = store.getSubjects(namedNode(SHACL.targetClass), namedNode(classIri), null);
|
|
735
|
+
const contributingShapes = new Set();
|
|
736
|
+
let mergedCardinality = null;
|
|
737
|
+
const valueTypes = new Set();
|
|
738
|
+
for (const nodeShape of nodeShapes) {
|
|
739
|
+
const propertyShapes = store.getObjects(nodeShape, namedNode(SHACL.property), null);
|
|
740
|
+
for (const propertyShape of propertyShapes) {
|
|
741
|
+
const pathNode = store.getObjects(propertyShape, namedNode(SHACL.path), null)[0];
|
|
742
|
+
if (!pathNode || pathNode.termType !== 'NamedNode' || pathNode.value !== propertyIri) {
|
|
743
|
+
continue;
|
|
744
|
+
}
|
|
745
|
+
contributingShapes.add(termIdentity(nodeShape));
|
|
746
|
+
const min = parseShaclCountLiteral(store.getObjects(propertyShape, namedNode(SHACL.minCount), null)[0]);
|
|
747
|
+
const max = parseShaclCountLiteral(store.getObjects(propertyShape, namedNode(SHACL.maxCount), null)[0]);
|
|
748
|
+
mergedCardinality = mergeCardinality(mergedCardinality, {
|
|
749
|
+
min: min === null ? undefined : min,
|
|
750
|
+
max: max === null ? undefined : max,
|
|
751
|
+
});
|
|
752
|
+
appendValueTypeTerms(valueTypes, store.getObjects(propertyShape, namedNode(SHACL.datatype), null));
|
|
753
|
+
appendValueTypeTerms(valueTypes, store.getObjects(propertyShape, namedNode(SHACL.class), null));
|
|
754
|
+
appendValueTypeTerms(valueTypes, store.getObjects(propertyShape, namedNode(SHACL.node), null));
|
|
755
|
+
appendValueTypeTerms(valueTypes, store.getObjects(propertyShape, namedNode(SHACL.nodeKind), null));
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
return {
|
|
759
|
+
cardinality: mergedCardinality,
|
|
760
|
+
contributingShapeCount: contributingShapes.size,
|
|
761
|
+
valueTypes: Array.from(valueTypes),
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
function appendValueTypeTerms(target, terms) {
|
|
765
|
+
for (const term of terms) {
|
|
766
|
+
target.add(termToDisplayText(term));
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
function termToDisplayText(term) {
|
|
770
|
+
if (term.termType === 'NamedNode') {
|
|
771
|
+
return getLocalName(term.value);
|
|
772
|
+
}
|
|
773
|
+
if (term.termType === 'Literal') {
|
|
774
|
+
return term.value;
|
|
775
|
+
}
|
|
776
|
+
return term.value;
|
|
777
|
+
}
|
|
778
|
+
function termIdentity(term) {
|
|
779
|
+
if (term.termType === 'BlankNode') {
|
|
780
|
+
return `_:${term.value}`;
|
|
781
|
+
}
|
|
782
|
+
return term.value;
|
|
783
|
+
}
|
|
784
|
+
function resolveValueType(shaclTypes, rdfsRanges) {
|
|
785
|
+
if (shaclTypes.length > 0) {
|
|
786
|
+
return shaclTypes.join(' | ');
|
|
787
|
+
}
|
|
788
|
+
if (rdfsRanges.length > 0) {
|
|
789
|
+
return rdfsRanges.join(' | ');
|
|
790
|
+
}
|
|
791
|
+
return 'unspecified';
|
|
792
|
+
}
|
|
793
|
+
function getShaclTargetClassesForProperty(propertyIri, store) {
|
|
794
|
+
const namedNode = N3.DataFactory.namedNode;
|
|
795
|
+
const results = new Set();
|
|
796
|
+
const nodeShapes = store.getSubjects(null, namedNode(SHACL.property), null);
|
|
797
|
+
for (const nodeShape of nodeShapes) {
|
|
798
|
+
const propertyShapes = store.getObjects(nodeShape, namedNode(SHACL.property), null);
|
|
799
|
+
for (const propertyShape of propertyShapes) {
|
|
800
|
+
const pathNode = store.getObjects(propertyShape, namedNode(SHACL.path), null)[0];
|
|
801
|
+
if (!pathNode || pathNode.termType !== 'NamedNode' || pathNode.value !== propertyIri) {
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
804
|
+
const targetClasses = store.getObjects(nodeShape, namedNode(SHACL.targetClass), null);
|
|
805
|
+
for (const targetClass of targetClasses) {
|
|
806
|
+
if (targetClass.termType === 'NamedNode') {
|
|
807
|
+
results.add(targetClass.value);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
return Array.from(results);
|
|
813
|
+
}
|
|
814
|
+
function getNamedNodeObjectValues(store, subjectIri, predicateIri) {
|
|
815
|
+
const objects = store.getObjects(N3.DataFactory.namedNode(subjectIri), N3.DataFactory.namedNode(predicateIri), null);
|
|
816
|
+
const values = objects
|
|
817
|
+
.filter((term) => term.termType === 'NamedNode')
|
|
818
|
+
.map((term) => term.value);
|
|
819
|
+
return Array.from(new Set(values));
|
|
820
|
+
}
|
|
821
|
+
function getFirstLiteralObjectValue(store, subjectIri, predicateIri) {
|
|
822
|
+
const object = store.getObjects(N3.DataFactory.namedNode(subjectIri), N3.DataFactory.namedNode(predicateIri), null)[0];
|
|
823
|
+
return object?.termType === 'Literal' ? object.value : undefined;
|
|
824
|
+
}
|
|
825
|
+
function getLocalName(iri) {
|
|
826
|
+
return iri.split(/[#/]/).pop() || iri;
|
|
827
|
+
}
|
|
828
|
+
function mergeCardinality(current, incoming) {
|
|
829
|
+
if (!current)
|
|
830
|
+
return incoming;
|
|
831
|
+
const merged = {};
|
|
832
|
+
const currentMin = current.min ?? 0;
|
|
833
|
+
const incomingMin = incoming.min ?? 0;
|
|
834
|
+
const min = Math.max(currentMin, incomingMin);
|
|
835
|
+
if (min > 0) {
|
|
836
|
+
merged.min = min;
|
|
837
|
+
}
|
|
838
|
+
if (current.max === undefined && incoming.max !== undefined) {
|
|
839
|
+
merged.max = incoming.max;
|
|
840
|
+
}
|
|
841
|
+
else if (current.max !== undefined && incoming.max === undefined) {
|
|
842
|
+
merged.max = current.max;
|
|
843
|
+
}
|
|
844
|
+
else if (current.max !== undefined && incoming.max !== undefined) {
|
|
845
|
+
merged.max = Math.min(current.max, incoming.max);
|
|
846
|
+
}
|
|
847
|
+
return merged;
|
|
848
|
+
}
|
|
849
|
+
function formatRequirement(summary) {
|
|
850
|
+
const { cardinality, hasConflict, contributingShapeCount } = summary;
|
|
851
|
+
if (hasConflict && cardinality) {
|
|
852
|
+
const minValue = cardinality.min ?? 0;
|
|
853
|
+
const maxValue = cardinality.max === undefined ? '*' : String(cardinality.max);
|
|
854
|
+
return `conflicting constraints (${minValue}..${maxValue})`;
|
|
855
|
+
}
|
|
856
|
+
if (!cardinality || (cardinality.min === undefined && cardinality.max === undefined)) {
|
|
857
|
+
return 'optional (unspecified)';
|
|
858
|
+
}
|
|
859
|
+
const min = cardinality.min ?? 0;
|
|
860
|
+
const max = cardinality.max;
|
|
861
|
+
let base = 'optional (0..*)';
|
|
862
|
+
if (min >= 1 && max === 1)
|
|
863
|
+
base = 'required (1..1)';
|
|
864
|
+
else if (min >= 1 && max !== undefined)
|
|
865
|
+
base = `required (${min}..${max})`;
|
|
866
|
+
else if (min >= 1)
|
|
867
|
+
base = `required (${min}..*)`;
|
|
868
|
+
else if (max === 1)
|
|
869
|
+
base = 'optional (0..1)';
|
|
870
|
+
else if (max !== undefined)
|
|
871
|
+
base = `optional (0..${max})`;
|
|
872
|
+
if (contributingShapeCount > 1 && base !== 'optional (unspecified)') {
|
|
873
|
+
return `${base} [effective across ${contributingShapeCount} shapes]`;
|
|
874
|
+
}
|
|
875
|
+
return base;
|
|
876
|
+
}
|
|
877
|
+
function parseShaclCountLiteral(term) {
|
|
878
|
+
if (!term)
|
|
879
|
+
return null;
|
|
880
|
+
if (term.termType !== 'Literal')
|
|
881
|
+
return null;
|
|
882
|
+
const parsed = Number(term.value);
|
|
883
|
+
if (!Number.isFinite(parsed) || !Number.isInteger(parsed) || parsed < 0) {
|
|
884
|
+
return null;
|
|
885
|
+
}
|
|
886
|
+
return parsed;
|
|
887
|
+
}
|
|
888
|
+
function hasCardinalityConflict(cardinality) {
|
|
889
|
+
if (!cardinality)
|
|
890
|
+
return false;
|
|
891
|
+
if (cardinality.min === undefined || cardinality.max === undefined)
|
|
892
|
+
return false;
|
|
893
|
+
return cardinality.min > cardinality.max;
|
|
894
|
+
}
|
|
895
|
+
function generateClassProseFromJson(node, sourcePos) {
|
|
896
|
+
const blocks = [];
|
|
897
|
+
const comment = node['rdfs:comment'] || node['comment'] || node['description'];
|
|
898
|
+
if (comment) {
|
|
899
|
+
const commentText = typeof comment === 'string'
|
|
900
|
+
? comment
|
|
901
|
+
: comment['@value'] || '';
|
|
902
|
+
blocks.push({
|
|
903
|
+
type: 'paragraph',
|
|
904
|
+
children: [{ type: 'text', value: commentText }],
|
|
905
|
+
sourcePos
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
blocks.push({
|
|
909
|
+
type: 'paragraph',
|
|
910
|
+
children: [
|
|
911
|
+
{
|
|
912
|
+
type: 'text',
|
|
913
|
+
value: 'Informative fallback: JSON-LD term metadata was found, but normative schema extraction requires Turtle/SHACL side files.'
|
|
914
|
+
}
|
|
915
|
+
],
|
|
916
|
+
sourcePos
|
|
917
|
+
});
|
|
918
|
+
return blocks;
|
|
919
|
+
}
|
|
920
|
+
function generatePropertyProseFromJson(node, sourcePos) {
|
|
921
|
+
const blocks = [];
|
|
922
|
+
const comment = node['rdfs:comment'] || node['comment'] || node['description'];
|
|
923
|
+
if (comment) {
|
|
924
|
+
const commentText = typeof comment === 'string'
|
|
925
|
+
? comment
|
|
926
|
+
: comment['@value'] || '';
|
|
927
|
+
blocks.push({
|
|
928
|
+
type: 'paragraph',
|
|
929
|
+
children: [{ type: 'text', value: commentText }],
|
|
930
|
+
sourcePos
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
blocks.push({
|
|
934
|
+
type: 'paragraph',
|
|
935
|
+
children: [
|
|
936
|
+
{
|
|
937
|
+
type: 'text',
|
|
938
|
+
value: 'Informative fallback: JSON-LD property metadata was found, but normative schema extraction requires Turtle/SHACL side files.'
|
|
939
|
+
}
|
|
940
|
+
],
|
|
941
|
+
sourcePos
|
|
942
|
+
});
|
|
943
|
+
return blocks;
|
|
944
|
+
}
|
|
945
|
+
function generateContextProse(model, sourcePos, showExpandedIri = false) {
|
|
946
|
+
const blocks = [];
|
|
947
|
+
// @vocab
|
|
948
|
+
if (model.vocab) {
|
|
949
|
+
blocks.push({
|
|
950
|
+
type: 'paragraph',
|
|
951
|
+
children: [
|
|
952
|
+
{ type: 'text', value: 'The ' },
|
|
953
|
+
{ type: 'inlineCode', value: model.name },
|
|
954
|
+
{ type: 'text', value: ' JSON-LD context ' },
|
|
955
|
+
{ type: 'requirement', keyword: 'MUST' },
|
|
956
|
+
{ type: 'text', value: ' set ' },
|
|
957
|
+
{ type: 'inlineCode', value: '@vocab' },
|
|
958
|
+
{ type: 'text', value: ' to the ' },
|
|
959
|
+
{ type: 'inlineCode', value: model.vocab },
|
|
960
|
+
{ type: 'text', value: ' namespace.' }
|
|
961
|
+
],
|
|
962
|
+
sourcePos
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
// Term mappings
|
|
966
|
+
for (const rule of model.terms) {
|
|
967
|
+
if (rule.isNest) {
|
|
968
|
+
blocks.push({
|
|
969
|
+
type: 'paragraph',
|
|
970
|
+
children: [
|
|
971
|
+
{ type: 'text', value: 'The ' },
|
|
972
|
+
{ type: 'inlineCode', value: rule.term },
|
|
973
|
+
{ type: 'text', value: ' term is an ' },
|
|
974
|
+
{ type: 'inlineCode', value: '@nest' },
|
|
975
|
+
{ type: 'text', value: ' alias; nested members ' },
|
|
976
|
+
{ type: 'requirement', keyword: 'MUST' },
|
|
977
|
+
{ type: 'text', value: ' be interpreted as direct properties.' }
|
|
978
|
+
],
|
|
979
|
+
sourcePos
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
else if (rule.isAlias) {
|
|
983
|
+
blocks.push({
|
|
984
|
+
type: 'paragraph',
|
|
985
|
+
children: [
|
|
986
|
+
{ type: 'text', value: 'The ' },
|
|
987
|
+
{ type: 'inlineCode', value: rule.term },
|
|
988
|
+
{ type: 'text', value: ' term ' },
|
|
989
|
+
{ type: 'requirement', keyword: 'MUST' },
|
|
990
|
+
{ type: 'text', value: ' be an alias for the JSON-LD ' },
|
|
991
|
+
{ type: 'inlineCode', value: rule.targetKeyword },
|
|
992
|
+
{ type: 'text', value: ' keyword.' }
|
|
993
|
+
],
|
|
994
|
+
sourcePos
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
else if (rule.iri) {
|
|
998
|
+
const expandedIri = showExpandedIri ? expandContextIri(rule.iri, model) : rule.iri;
|
|
999
|
+
if (rule.isPrefix) {
|
|
1000
|
+
blocks.push({
|
|
1001
|
+
type: 'paragraph',
|
|
1002
|
+
children: [
|
|
1003
|
+
{ type: 'text', value: 'The ' },
|
|
1004
|
+
{ type: 'inlineCode', value: rule.term },
|
|
1005
|
+
{ type: 'text', value: ' prefix ' },
|
|
1006
|
+
{ type: 'requirement', keyword: 'MUST' },
|
|
1007
|
+
{ type: 'text', value: ' expand to ' },
|
|
1008
|
+
{ type: 'inlineCode', value: expandedIri },
|
|
1009
|
+
{ type: 'text', value: '.' }
|
|
1010
|
+
],
|
|
1011
|
+
sourcePos
|
|
1012
|
+
});
|
|
1013
|
+
continue;
|
|
1014
|
+
}
|
|
1015
|
+
if (rule.typeCoercion === '@id') {
|
|
1016
|
+
blocks.push({
|
|
1017
|
+
type: 'paragraph',
|
|
1018
|
+
children: [
|
|
1019
|
+
{ type: 'text', value: 'The ' },
|
|
1020
|
+
{ type: 'inlineCode', value: rule.term },
|
|
1021
|
+
{ type: 'text', value: ' term maps to ' },
|
|
1022
|
+
{ type: 'inlineCode', value: expandedIri },
|
|
1023
|
+
{ type: 'text', value: ' and values ' },
|
|
1024
|
+
{ type: 'requirement', keyword: 'MUST' },
|
|
1025
|
+
{ type: 'text', value: ' be interpreted as IRIs.' }
|
|
1026
|
+
],
|
|
1027
|
+
sourcePos
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
else if (rule.typeCoercion === '@json') {
|
|
1031
|
+
blocks.push({
|
|
1032
|
+
type: 'paragraph',
|
|
1033
|
+
children: [
|
|
1034
|
+
{ type: 'text', value: 'The ' },
|
|
1035
|
+
{ type: 'inlineCode', value: rule.term },
|
|
1036
|
+
{ type: 'text', value: ' term ' },
|
|
1037
|
+
{ type: 'requirement', keyword: 'MAY' },
|
|
1038
|
+
{ type: 'text', value: ' be represented as an ' },
|
|
1039
|
+
{ type: 'inlineCode', value: '@json' },
|
|
1040
|
+
{ type: 'text', value: ' literal.' }
|
|
1041
|
+
],
|
|
1042
|
+
sourcePos
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
else if (rule.typeCoercion) {
|
|
1046
|
+
blocks.push({
|
|
1047
|
+
type: 'paragraph',
|
|
1048
|
+
children: [
|
|
1049
|
+
{ type: 'text', value: 'The ' },
|
|
1050
|
+
{ type: 'inlineCode', value: rule.term },
|
|
1051
|
+
{ type: 'text', value: ' term maps to ' },
|
|
1052
|
+
{ type: 'inlineCode', value: expandedIri },
|
|
1053
|
+
{ type: 'text', value: ' and values ' },
|
|
1054
|
+
{ type: 'requirement', keyword: 'MUST' },
|
|
1055
|
+
{ type: 'text', value: ' be of type ' },
|
|
1056
|
+
{ type: 'inlineCode', value: expandContextType(rule.typeCoercion, model) },
|
|
1057
|
+
{ type: 'text', value: '.' }
|
|
1058
|
+
],
|
|
1059
|
+
sourcePos
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1062
|
+
else {
|
|
1063
|
+
blocks.push({
|
|
1064
|
+
type: 'paragraph',
|
|
1065
|
+
children: [
|
|
1066
|
+
{ type: 'text', value: 'The ' },
|
|
1067
|
+
{ type: 'inlineCode', value: rule.term },
|
|
1068
|
+
{ type: 'text', value: ' term ' },
|
|
1069
|
+
{ type: 'requirement', keyword: 'MUST' },
|
|
1070
|
+
{ type: 'text', value: ' map to ' },
|
|
1071
|
+
{ type: 'inlineCode', value: expandedIri },
|
|
1072
|
+
{ type: 'text', value: '.' }
|
|
1073
|
+
],
|
|
1074
|
+
sourcePos
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
if (rule.container === '@set') {
|
|
1078
|
+
blocks.push({
|
|
1079
|
+
type: 'paragraph',
|
|
1080
|
+
children: [
|
|
1081
|
+
{ type: 'text', value: 'The ' },
|
|
1082
|
+
{ type: 'inlineCode', value: rule.term },
|
|
1083
|
+
{ type: 'text', value: ' term uses ' },
|
|
1084
|
+
{ type: 'inlineCode', value: '@set' },
|
|
1085
|
+
{ type: 'text', value: '; processors ' },
|
|
1086
|
+
{ type: 'requirement', keyword: 'MUST' },
|
|
1087
|
+
{ type: 'text', value: ' accept array form consistently for single or multiple values.' }
|
|
1088
|
+
],
|
|
1089
|
+
sourcePos
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
return blocks;
|
|
1095
|
+
}
|
|
1096
|
+
function isContextPrefixDeclaration(term, iri) {
|
|
1097
|
+
if (term.includes(':'))
|
|
1098
|
+
return false;
|
|
1099
|
+
if (!isAbsoluteIri(iri))
|
|
1100
|
+
return false;
|
|
1101
|
+
return iri.endsWith('/') || iri.endsWith('#');
|
|
1102
|
+
}
|
|
1103
|
+
function isAbsoluteIri(value) {
|
|
1104
|
+
return /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(value);
|
|
1105
|
+
}
|
|
1106
|
+
function expandContextIri(value, model) {
|
|
1107
|
+
if (!value || value.startsWith('@'))
|
|
1108
|
+
return value;
|
|
1109
|
+
if (isAbsoluteIri(value)) {
|
|
1110
|
+
const colonIndex = value.indexOf(':');
|
|
1111
|
+
const prefix = colonIndex === -1 ? value : value.slice(0, colonIndex);
|
|
1112
|
+
if (colonIndex !== -1 && model.prefixes[prefix]) {
|
|
1113
|
+
return model.prefixes[prefix] + value.slice(colonIndex + 1);
|
|
1114
|
+
}
|
|
1115
|
+
return value;
|
|
1116
|
+
}
|
|
1117
|
+
if (value.includes(':')) {
|
|
1118
|
+
const colonIndex = value.indexOf(':');
|
|
1119
|
+
const prefix = value.slice(0, colonIndex);
|
|
1120
|
+
const local = value.slice(colonIndex + 1);
|
|
1121
|
+
const ns = model.prefixes[prefix];
|
|
1122
|
+
if (ns)
|
|
1123
|
+
return ns + local;
|
|
1124
|
+
return value;
|
|
1125
|
+
}
|
|
1126
|
+
if (model.vocab) {
|
|
1127
|
+
return `${model.vocab}${value}`;
|
|
1128
|
+
}
|
|
1129
|
+
return value;
|
|
1130
|
+
}
|
|
1131
|
+
function expandContextType(value, model) {
|
|
1132
|
+
if (value.startsWith('@'))
|
|
1133
|
+
return value;
|
|
1134
|
+
return expandContextIri(value, model);
|
|
1135
|
+
}
|
|
1136
|
+
// ============================================================================
|
|
1137
|
+
// Cell helpers
|
|
1138
|
+
// ============================================================================
|
|
1139
|
+
function createHeaderCell(text) {
|
|
1140
|
+
return {
|
|
1141
|
+
type: 'tableCell',
|
|
1142
|
+
header: true,
|
|
1143
|
+
children: [{ type: 'text', value: text }]
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
function createCell(text, code = false) {
|
|
1147
|
+
return {
|
|
1148
|
+
type: 'tableCell',
|
|
1149
|
+
children: [
|
|
1150
|
+
code
|
|
1151
|
+
? { type: 'inlineCode', value: text }
|
|
1152
|
+
: { type: 'text', value: text }
|
|
1153
|
+
]
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
//# sourceMappingURL=VocabHtmlParser.js.map
|