eyeling 1.32.0 → 1.32.1
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 +37 -3
- package/browser.d.ts +63 -0
- package/dist/browser/eyeling.browser.js +297 -14
- package/eyeling-builtins.ttl +3 -3
- package/eyeling.js +297 -14
- package/index.d.ts +221 -262
- package/lib/builtins.js +297 -14
- package/package.json +4 -3
- package/test/builtins.test.js +49 -1
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ Eyeling is characterized by:
|
|
|
22
22
|
- **Built-ins in rule bodies** — N3 programs can combine logical rules with computations such as math, string, datatype, list, time, and web-oriented predicates.
|
|
23
23
|
- **Streaming RDF Messages** — supports RDF Messages streams, enabling Eyeling to fit into streaming RDF pipelines.
|
|
24
24
|
- **Node.js, npm, and browser use** — run it from the command line, call it from JavaScript, or use the browser-oriented bundle.
|
|
25
|
-
- **RDF-JS interoperability** — use N3 text, RDF-JS quads, datasets, or Eyeling’s own AST-level API
|
|
25
|
+
- **RDF-JS interoperability** — use N3 text, RDF-JS quads, datasets, or Eyeling’s own AST-level API, with TypeScript declarations grounded in `@rdfjs/types`.
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
This README is the primary guide to using, extending, and maintaining Eyeling.
|
|
@@ -533,7 +533,7 @@ The browser entry loads `dist/browser/eyeling.browser.js` and exposes the API th
|
|
|
533
533
|
|
|
534
534
|
## RDF-JS integration
|
|
535
535
|
|
|
536
|
-
Eyeling includes a lightweight RDF-JS DataFactory and adapters for supported RDF-JS terms and quads.
|
|
536
|
+
Eyeling includes a lightweight RDF-JS DataFactory and adapters for supported RDF-JS terms and quads. Its TypeScript declarations reuse the official `@rdfjs/types` interfaces, so Eyeling quads, terms, and data factories can be passed to other RDF-JS libraries without local type casts.
|
|
537
537
|
|
|
538
538
|
```js
|
|
539
539
|
const { reasonStream, rdfjs } = require('eyeling');
|
|
@@ -558,6 +558,37 @@ const result = reasonStream(input, { rdfjs: true });
|
|
|
558
558
|
console.log(result.closureQuads);
|
|
559
559
|
```
|
|
560
560
|
|
|
561
|
+
TypeScript users can use Eyeling directly with `@rdfjs/types`:
|
|
562
|
+
|
|
563
|
+
```ts
|
|
564
|
+
import type { DataFactory, Quad } from '@rdfjs/types';
|
|
565
|
+
import { rdfjs, reasonRdfJs } from 'eyeling';
|
|
566
|
+
|
|
567
|
+
const ex = 'http://example.org/';
|
|
568
|
+
const factory: DataFactory<Quad> = rdfjs;
|
|
569
|
+
|
|
570
|
+
const facts: Quad[] = [
|
|
571
|
+
factory.quad(
|
|
572
|
+
factory.namedNode(`${ex}Socrates`),
|
|
573
|
+
factory.namedNode(`${ex}type`),
|
|
574
|
+
factory.namedNode(`${ex}Man`),
|
|
575
|
+
),
|
|
576
|
+
];
|
|
577
|
+
|
|
578
|
+
for await (const quad of reasonRdfJs({
|
|
579
|
+
quads: facts,
|
|
580
|
+
n3: `
|
|
581
|
+
@prefix : <http://example.org/> .
|
|
582
|
+
{ ?x :type :Man } => { ?x :type :Mortal } .
|
|
583
|
+
`,
|
|
584
|
+
})) {
|
|
585
|
+
const derived: Quad = quad;
|
|
586
|
+
console.log(derived.subject.value, derived.predicate.value, derived.object.value);
|
|
587
|
+
}
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
The built-in `rdfjs` factory implements the standard RDF-JS constructors for named nodes, blank nodes, literals, default graph terms, variables, and quads, plus `fromTerm()` and `fromQuad()` clone helpers. `@rdfjs/types` is installed with Eyeling because the public declarations import it.
|
|
591
|
+
|
|
561
592
|
Supported RDF-JS input terms include named nodes, blank nodes, literals, variables, default graph terms, and default-graph quads. Named-graph input quads are rejected clearly unless handled through N3/TriG compatibility mode.
|
|
562
593
|
|
|
563
594
|
Use RDF-JS when you want Eyeling to sit inside a JavaScript RDF pipeline. Use raw N3 input when you need N3-only features such as quoted formulas or N3 rules represented directly in source text.
|
|
@@ -694,17 +725,20 @@ Supported operations include:
|
|
|
694
725
|
- `dt:sameValueAs` and `dt:differentValueFrom` for value-space equality and inequality;
|
|
695
726
|
- `dt:canonicalLiteral` for canonical literal production.
|
|
696
727
|
|
|
697
|
-
The built-ins use strict datatype lexical validation for these checks, including exact dateTime rollover/canonicalization such as `24:00:00` normalizing to the following day. They cover RDF language strings and the OWL 2 RL-relevant XSD set: `xsd:string`, `xsd:normalizedString`, `xsd:token`, `xsd:language`, `xsd:Name`, `xsd:NCName`, `xsd:NMTOKEN`, `xsd:boolean`, `xsd:decimal`, `xsd:integer` and its bounded integer subtypes, `xsd:float`, `xsd:double`, `xsd:hexBinary`, `xsd:base64Binary`, `xsd:anyURI`, `xsd:dateTime`, and `xsd:dateTimeStamp`.
|
|
728
|
+
The built-ins use strict datatype lexical validation for these checks, including exact dateTime rollover/canonicalization such as `24:00:00` normalizing to the following day. They cover RDF language strings, `rdf:PlainLiteral`, `rdf:XMLLiteral`, `rdfs:Literal`, and the OWL 2 RL-relevant XSD set: `xsd:string`, `xsd:normalizedString`, `xsd:token`, `xsd:language`, `xsd:Name`, `xsd:NCName`, `xsd:NMTOKEN`, `xsd:boolean`, `xsd:decimal`, `xsd:integer` and its bounded integer subtypes, `xsd:float`, `xsd:double`, `xsd:hexBinary`, `xsd:base64Binary`, `xsd:anyURI`, `xsd:dateTime`, and `xsd:dateTimeStamp`. Lexical validation is intentionally strict for conformance: string-derived datatypes with whitespace-collapse facets must already be written in canonical collapsed lexical form, `xsd:float` and `xsd:double` enforce finite value ranges, `xsd:anyURI` rejects spaces, unsafe delimiters, and malformed percent escapes, and XML literals must be well-formed XML fragments.
|
|
698
729
|
|
|
699
730
|
Example:
|
|
700
731
|
|
|
701
732
|
```n3
|
|
702
733
|
@prefix : <http://example.org/> .
|
|
703
734
|
@prefix dt: <https://eyereasoner.github.io/eyeling/datatype#> .
|
|
735
|
+
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
|
704
736
|
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
705
737
|
|
|
706
738
|
{
|
|
707
739
|
"01"^^xsd:integer dt:sameValueAs "1.0"^^xsd:decimal .
|
|
740
|
+
"hello@EN"^^rdf:PlainLiteral dt:sameValueAs "hello@en"^^rdf:PlainLiteral .
|
|
741
|
+
"<a/>"^^rdf:XMLLiteral dt:validForDatatype rdf:XMLLiteral .
|
|
708
742
|
"2026-06-10T12:00:00Z"^^xsd:dateTime
|
|
709
743
|
dt:sameValueAs "2026-06-10T14:00:00+02:00"^^xsd:dateTime .
|
|
710
744
|
("abc"^^xsd:integer xsd:integer) dt:validForDatatype false .
|
package/browser.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export type EyelingPrefixEnv = import('eyeling').EyelingPrefixEnv;
|
|
2
|
+
export type EyelingIri = import('eyeling').EyelingIri;
|
|
3
|
+
export type EyelingLiteral = import('eyeling').EyelingLiteral;
|
|
4
|
+
export type EyelingVar = import('eyeling').EyelingVar;
|
|
5
|
+
export type EyelingBlank = import('eyeling').EyelingBlank;
|
|
6
|
+
export type EyelingListTerm = import('eyeling').EyelingListTerm;
|
|
7
|
+
export type EyelingOpenListTerm = import('eyeling').EyelingOpenListTerm;
|
|
8
|
+
export type EyelingGraphTerm = import('eyeling').EyelingGraphTerm;
|
|
9
|
+
export type EyelingTerm = import('eyeling').EyelingTerm;
|
|
10
|
+
export type EyelingTriple = import('eyeling').EyelingTriple;
|
|
11
|
+
export type EyelingRule = import('eyeling').EyelingRule;
|
|
12
|
+
export type EyelingAstBundle = import('eyeling').EyelingAstBundle;
|
|
13
|
+
export type N3Source = import('eyeling').N3Source;
|
|
14
|
+
export type N3SourceListInput = import('eyeling').N3SourceListInput;
|
|
15
|
+
export type RdfJsReasonInput = import('eyeling').RdfJsReasonInput;
|
|
16
|
+
export type StoreOptions = import('eyeling').StoreOptions;
|
|
17
|
+
export type BuiltinRegistrationContext = import('eyeling').BuiltinRegistrationContext;
|
|
18
|
+
export type BuiltinHandler = import('eyeling').BuiltinHandler;
|
|
19
|
+
export type ReasonStreamOptions = import('eyeling').ReasonStreamOptions;
|
|
20
|
+
export type ReasonStreamResult = import('eyeling').ReasonStreamResult;
|
|
21
|
+
export type FactStore = import('eyeling').FactStore;
|
|
22
|
+
|
|
23
|
+
export function runAsync(
|
|
24
|
+
input: string | RdfJsReasonInput | EyelingAstBundle | N3SourceListInput,
|
|
25
|
+
opts?: ReasonStreamOptions,
|
|
26
|
+
): Promise<ReasonStreamResult & { store?: FactStore }>;
|
|
27
|
+
export function reasonStream(
|
|
28
|
+
input: string | RdfJsReasonInput | EyelingAstBundle | N3SourceListInput,
|
|
29
|
+
opts?: ReasonStreamOptions,
|
|
30
|
+
): ReasonStreamResult;
|
|
31
|
+
export function reasonRdfJs(
|
|
32
|
+
input: string | RdfJsReasonInput | EyelingAstBundle | N3SourceListInput,
|
|
33
|
+
opts?: Omit<ReasonStreamOptions, 'rdfjs' | 'onDerived'>,
|
|
34
|
+
): AsyncIterable<import('@rdfjs/types').Quad>;
|
|
35
|
+
|
|
36
|
+
export const INFERENCE_FUSE_EXIT_CODE: 65;
|
|
37
|
+
export const rdfjs: import('eyeling').EyelingModule['rdfjs'];
|
|
38
|
+
export function createFactStore(options?: string | StoreOptions | null): Promise<FactStore>;
|
|
39
|
+
export function registerBuiltin(iri: string, handler: BuiltinHandler): BuiltinHandler;
|
|
40
|
+
export function unregisterBuiltin(iri: string): boolean;
|
|
41
|
+
export function registerBuiltinModule(mod: unknown, origin?: string): boolean;
|
|
42
|
+
export function listBuiltinIris(): string[];
|
|
43
|
+
export function collectOutputStringsFromFacts(facts: EyelingTriple[], prefixes: EyelingPrefixEnv): string[];
|
|
44
|
+
export function prettyPrintQueryTriples(triples: EyelingTriple[], prefixes: EyelingPrefixEnv): string;
|
|
45
|
+
|
|
46
|
+
export interface EyelingBrowserModule {
|
|
47
|
+
readonly version: string;
|
|
48
|
+
runAsync: typeof runAsync;
|
|
49
|
+
reasonStream: typeof reasonStream;
|
|
50
|
+
reasonRdfJs: typeof reasonRdfJs;
|
|
51
|
+
readonly INFERENCE_FUSE_EXIT_CODE: typeof INFERENCE_FUSE_EXIT_CODE;
|
|
52
|
+
rdfjs: typeof rdfjs;
|
|
53
|
+
createFactStore: typeof createFactStore;
|
|
54
|
+
registerBuiltin: typeof registerBuiltin;
|
|
55
|
+
unregisterBuiltin: typeof unregisterBuiltin;
|
|
56
|
+
registerBuiltinModule: typeof registerBuiltinModule;
|
|
57
|
+
listBuiltinIris: typeof listBuiltinIris;
|
|
58
|
+
collectOutputStringsFromFacts: typeof collectOutputStringsFromFacts;
|
|
59
|
+
prettyPrintQueryTriples: typeof prettyPrintQueryTriples;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
declare const eyeling: EyelingBrowserModule;
|
|
63
|
+
export default eyeling;
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
|
|
20
20
|
const {
|
|
21
21
|
RDF_NS,
|
|
22
|
+
RDFS_NS,
|
|
23
|
+
OWL_NS,
|
|
22
24
|
XSD_NS,
|
|
23
25
|
CRYPTO_NS,
|
|
24
26
|
MATH_NS,
|
|
@@ -1046,6 +1048,9 @@ const XSD_HEX_BINARY_DT = XSD_NS + 'hexBinary';
|
|
|
1046
1048
|
const XSD_BASE64_BINARY_DT = XSD_NS + 'base64Binary';
|
|
1047
1049
|
const XSD_ANY_URI_DT = XSD_NS + 'anyURI';
|
|
1048
1050
|
const RDF_LANGSTRING_DT = RDF_NS + 'langString';
|
|
1051
|
+
const RDF_PLAIN_LITERAL_DT = RDF_NS + 'PlainLiteral';
|
|
1052
|
+
const RDF_XML_LITERAL_DT = RDF_NS + 'XMLLiteral';
|
|
1053
|
+
const RDFS_LITERAL_DT = RDFS_NS + 'Literal';
|
|
1049
1054
|
|
|
1050
1055
|
const XSD_STRING_LIKE_DTS = new Set([
|
|
1051
1056
|
XSD_STRING_DT,
|
|
@@ -1074,6 +1079,9 @@ const XSD_INTEGER_BOUNDS = new Map([
|
|
|
1074
1079
|
|
|
1075
1080
|
const XSD_DATATYPE_BUILTIN_DTS = new Set([
|
|
1076
1081
|
RDF_LANGSTRING_DT,
|
|
1082
|
+
RDF_PLAIN_LITERAL_DT,
|
|
1083
|
+
RDF_XML_LITERAL_DT,
|
|
1084
|
+
RDFS_LITERAL_DT,
|
|
1077
1085
|
XSD_STRING_DT,
|
|
1078
1086
|
XSD_NS + 'normalizedString',
|
|
1079
1087
|
XSD_NS + 'token',
|
|
@@ -1095,6 +1103,11 @@ const XSD_DATATYPE_BUILTIN_DTS = new Set([
|
|
|
1095
1103
|
]);
|
|
1096
1104
|
|
|
1097
1105
|
|
|
1106
|
+
const XSD_FLOAT_MAX_VALUE = 3.4028234663852886e38;
|
|
1107
|
+
const XSD_FLOAT_MIN_POSITIVE_VALUE = 1.401298464324817e-45;
|
|
1108
|
+
const XSD_DOUBLE_MAX_VALUE = Number.MAX_VALUE;
|
|
1109
|
+
const XSD_DOUBLE_MIN_POSITIVE_VALUE = Number.MIN_VALUE;
|
|
1110
|
+
|
|
1098
1111
|
// Integer-derived datatypes from XML Schema Part 2 (and commonly used ones).
|
|
1099
1112
|
const XSD_INTEGER_DERIVED_DTS = new Set([
|
|
1100
1113
|
XSD_INTEGER_DT,
|
|
@@ -1153,13 +1166,31 @@ function inferDatatypeForShorthandLexical(lex) {
|
|
|
1153
1166
|
|
|
1154
1167
|
function extractLiteralLanguageTag(litVal) {
|
|
1155
1168
|
if (typeof litVal !== 'string' || !literalHasLangTag(litVal)) return null;
|
|
1156
|
-
|
|
1169
|
+
|
|
1170
|
+
let lastQuote;
|
|
1171
|
+
let quoteLen;
|
|
1172
|
+
if (litVal.startsWith('\"\"\"')) {
|
|
1173
|
+
lastQuote = litVal.lastIndexOf('\"\"\"');
|
|
1174
|
+
quoteLen = 3;
|
|
1175
|
+
} else {
|
|
1176
|
+
lastQuote = litVal.lastIndexOf('"');
|
|
1177
|
+
quoteLen = 1;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1157
1180
|
if (lastQuote < 0) return null;
|
|
1158
|
-
const after = lastQuote +
|
|
1181
|
+
const after = lastQuote + quoteLen;
|
|
1159
1182
|
if (after >= litVal.length || litVal[after] !== '@') return null;
|
|
1160
1183
|
const tag = litVal.slice(after + 1);
|
|
1161
|
-
if (
|
|
1162
|
-
return tag;
|
|
1184
|
+
if (!isValidLanguageTag(tag, true)) return null;
|
|
1185
|
+
return tag.toLowerCase();
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
function isValidLanguageTag(tag, allowDirectionalSuffix) {
|
|
1189
|
+
if (typeof tag !== 'string') return false;
|
|
1190
|
+
const pattern = allowDirectionalSuffix
|
|
1191
|
+
? /^[A-Za-z]{1,8}(?:-[A-Za-z0-9]{1,8})*(?:--(?:ltr|rtl))?$/
|
|
1192
|
+
: /^[A-Za-z]{1,8}(?:-[A-Za-z0-9]{1,8})*$/;
|
|
1193
|
+
return pattern.test(tag);
|
|
1163
1194
|
}
|
|
1164
1195
|
|
|
1165
1196
|
function literalLexicalValue(litVal) {
|
|
@@ -1218,17 +1249,18 @@ function canonicalFloatDoubleLex(n) {
|
|
|
1218
1249
|
}
|
|
1219
1250
|
|
|
1220
1251
|
function parseStringLikeDatatypeValue(lexical, dt) {
|
|
1221
|
-
|
|
1252
|
+
const value = String(lexical);
|
|
1222
1253
|
|
|
1223
1254
|
if (dt === XSD_NS + 'normalizedString') {
|
|
1224
|
-
|
|
1255
|
+
if (/[\t\n\r]/.test(value)) return null;
|
|
1225
1256
|
} else if (dt !== XSD_STRING_DT) {
|
|
1226
|
-
|
|
1257
|
+
// String-derived datatypes with whitespace facet collapse are considered
|
|
1258
|
+
// lexically valid only when the spelling is already in collapsed form.
|
|
1259
|
+
if (/[\t\n\r]/.test(value) || /^ | $/.test(value) || / {2,}/.test(value)) return null;
|
|
1227
1260
|
}
|
|
1228
1261
|
|
|
1229
1262
|
if (dt === XSD_NS + 'language') {
|
|
1230
|
-
if (
|
|
1231
|
-
value = value.toLowerCase();
|
|
1263
|
+
if (!isValidLanguageTag(value, false)) return null;
|
|
1232
1264
|
} else if (dt === XSD_NS + 'Name') {
|
|
1233
1265
|
if (!/^[:_\p{L}][:_\p{L}\p{N}.\-\u00B7\u0300-\u036F\u203F-\u2040]*$/u.test(value)) return null;
|
|
1234
1266
|
} else if (dt === XSD_NS + 'NCName') {
|
|
@@ -1240,9 +1272,51 @@ function parseStringLikeDatatypeValue(lexical, dt) {
|
|
|
1240
1272
|
return {
|
|
1241
1273
|
family: 'string',
|
|
1242
1274
|
dt,
|
|
1243
|
-
value,
|
|
1244
|
-
canonicalLex: value,
|
|
1245
|
-
canonicalLiteral: makeDatatypeTypedLiteral(value, dt),
|
|
1275
|
+
value: dt === XSD_NS + 'language' ? value.toLowerCase() : value,
|
|
1276
|
+
canonicalLex: dt === XSD_NS + 'language' ? value.toLowerCase() : value,
|
|
1277
|
+
canonicalLiteral: makeDatatypeTypedLiteral(dt === XSD_NS + 'language' ? value.toLowerCase() : value, dt),
|
|
1278
|
+
comparable: true,
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
function parsePlainLiteralDatatypeValue(lexical) {
|
|
1283
|
+
const parsed = splitPlainLiteralLexical(lexical);
|
|
1284
|
+
if (!parsed) return null;
|
|
1285
|
+
const canonicalLex = `${parsed.text}@${parsed.lang}`;
|
|
1286
|
+
return {
|
|
1287
|
+
family: 'plainLiteral',
|
|
1288
|
+
dt: RDF_PLAIN_LITERAL_DT,
|
|
1289
|
+
value: parsed.text,
|
|
1290
|
+
lang: parsed.lang,
|
|
1291
|
+
canonicalLex,
|
|
1292
|
+
canonicalLiteral: makeDatatypeTypedLiteral(canonicalLex, RDF_PLAIN_LITERAL_DT),
|
|
1293
|
+
comparable: true,
|
|
1294
|
+
};
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
function splitPlainLiteralLexical(lexical) {
|
|
1298
|
+
const s = String(lexical);
|
|
1299
|
+
const at = s.lastIndexOf('@');
|
|
1300
|
+
// rdf:PlainLiteral lexical forms encode the literal as text + "@" +
|
|
1301
|
+
// language-tag. The language tag may be empty, e.g. "abc@".
|
|
1302
|
+
// A lexical with no separator at all, e.g. "abc", is ill-typed.
|
|
1303
|
+
if (at < 0) return null;
|
|
1304
|
+
const text = s.slice(0, at);
|
|
1305
|
+
const lang = s.slice(at + 1);
|
|
1306
|
+
if (lang !== '' && !isValidLanguageTag(lang, false)) return null;
|
|
1307
|
+
return { text, lang: lang.toLowerCase() };
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
function parseXmlLiteralDatatypeValue(lexical) {
|
|
1311
|
+
const s = String(lexical);
|
|
1312
|
+
if (!isWellFormedXmlFragment(s)) return null;
|
|
1313
|
+
const canonicalLex = s.trim();
|
|
1314
|
+
return {
|
|
1315
|
+
family: 'xmlLiteral',
|
|
1316
|
+
dt: RDF_XML_LITERAL_DT,
|
|
1317
|
+
value: canonicalLex,
|
|
1318
|
+
canonicalLex,
|
|
1319
|
+
canonicalLiteral: makeDatatypeTypedLiteral(canonicalLex, RDF_XML_LITERAL_DT),
|
|
1246
1320
|
comparable: true,
|
|
1247
1321
|
};
|
|
1248
1322
|
}
|
|
@@ -1330,6 +1404,11 @@ function parseFloatDoubleDatatypeValue(lexical, dt) {
|
|
|
1330
1404
|
const special = parseXsdFloatSpecialLex(s);
|
|
1331
1405
|
const value = special !== null ? special : Number(s);
|
|
1332
1406
|
if (Number.isNaN(value) && s !== 'NaN') return null;
|
|
1407
|
+
if (Number.isFinite(value)) {
|
|
1408
|
+
const abs = Math.abs(value);
|
|
1409
|
+
if (dt === XSD_FLOAT_DT && abs > 0 && (abs > XSD_FLOAT_MAX_VALUE || abs < XSD_FLOAT_MIN_POSITIVE_VALUE)) return null;
|
|
1410
|
+
if (dt === XSD_DOUBLE_DT && abs > 0 && (abs > XSD_DOUBLE_MAX_VALUE || abs < XSD_DOUBLE_MIN_POSITIVE_VALUE)) return null;
|
|
1411
|
+
}
|
|
1333
1412
|
const canonicalLex = canonicalFloatDoubleLex(value);
|
|
1334
1413
|
return {
|
|
1335
1414
|
family: 'numeric',
|
|
@@ -1419,7 +1498,11 @@ function parseBinaryDatatypeValue(lexical, dt) {
|
|
|
1419
1498
|
|
|
1420
1499
|
function parseAnyUriDatatypeValue(lexical) {
|
|
1421
1500
|
const value = String(lexical);
|
|
1422
|
-
if (/[
|
|
1501
|
+
if (/[^\u0021-\u007E]/.test(value) || /[<>"{}|\\^`]/.test(value)) return null;
|
|
1502
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
1503
|
+
if (value[i] === '%' && !/^[0-9A-Fa-f]{2}$/.test(value.slice(i + 1, i + 3))) return null;
|
|
1504
|
+
}
|
|
1505
|
+
if (!isValidIriReferenceLexical(value)) return null;
|
|
1423
1506
|
return {
|
|
1424
1507
|
family: 'anyURI',
|
|
1425
1508
|
dt: XSD_ANY_URI_DT,
|
|
@@ -1430,6 +1513,116 @@ function parseAnyUriDatatypeValue(lexical) {
|
|
|
1430
1513
|
};
|
|
1431
1514
|
}
|
|
1432
1515
|
|
|
1516
|
+
function isValidIriReferenceLexical(value) {
|
|
1517
|
+
// Minimal RFC 3987/3986 structural check for IRI references. In
|
|
1518
|
+
// particular, a relative reference may not start with a colon-bearing first
|
|
1519
|
+
// path segment such as ":abc"; this is the OWL 2 RL anyURI illtyped case.
|
|
1520
|
+
// Empty references and fragment-only references are allowed.
|
|
1521
|
+
const s = String(value);
|
|
1522
|
+
if (s === '') return true;
|
|
1523
|
+
if (/^[A-Za-z][A-Za-z0-9+.-]*:/.test(s)) return true; // absolute scheme
|
|
1524
|
+
if (s.startsWith('//') || s.startsWith('/') || s.startsWith('?') || s.startsWith('#')) return true;
|
|
1525
|
+
const cut = s.search(/[/?#]/);
|
|
1526
|
+
const firstSegment = cut < 0 ? s : s.slice(0, cut);
|
|
1527
|
+
return !firstSegment.includes(':');
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
function isWellFormedXmlFragment(input) {
|
|
1531
|
+
const source = String(input).trim();
|
|
1532
|
+
if (!source) return false;
|
|
1533
|
+
const stack = [];
|
|
1534
|
+
let seenElement = false;
|
|
1535
|
+
let position = 0;
|
|
1536
|
+
|
|
1537
|
+
while (position < source.length) {
|
|
1538
|
+
const open = source.indexOf('<', position);
|
|
1539
|
+
if (open < 0) return stack.length === 0 && isValidXmlText(source.slice(position)) && seenElement;
|
|
1540
|
+
if (!isValidXmlText(source.slice(position, open))) return false;
|
|
1541
|
+
|
|
1542
|
+
if (source.startsWith('<!--', open)) {
|
|
1543
|
+
const close = source.indexOf('-->', open + 4);
|
|
1544
|
+
if (close < 0 || source.slice(open + 4, close).includes('--')) return false;
|
|
1545
|
+
position = close + 3;
|
|
1546
|
+
continue;
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
if (source.startsWith('<![CDATA[', open)) {
|
|
1550
|
+
const close = source.indexOf(']]>', open + 9);
|
|
1551
|
+
if (close < 0) return false;
|
|
1552
|
+
position = close + 3;
|
|
1553
|
+
continue;
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
if (source.startsWith('<?', open)) {
|
|
1557
|
+
const close = source.indexOf('?>', open + 2);
|
|
1558
|
+
if (close < 0) return false;
|
|
1559
|
+
position = close + 2;
|
|
1560
|
+
continue;
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
if (source.startsWith('</', open)) {
|
|
1564
|
+
const close = source.indexOf('>', open + 2);
|
|
1565
|
+
if (close < 0) return false;
|
|
1566
|
+
const name = source.slice(open + 2, close).trim();
|
|
1567
|
+
if (!isValidXmlName(name) || stack.pop() !== name) return false;
|
|
1568
|
+
position = close + 1;
|
|
1569
|
+
continue;
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
const close = findXmlTagEnd(source, open + 1);
|
|
1573
|
+
if (close < 0) return false;
|
|
1574
|
+
const raw = source.slice(open + 1, close);
|
|
1575
|
+
const selfClosing = /\/\s*$/.test(raw);
|
|
1576
|
+
const content = selfClosing ? raw.replace(/\/\s*$/, '').trim() : raw.trim();
|
|
1577
|
+
const nameMatch = /^([^\s/>]+)/.exec(content);
|
|
1578
|
+
if (!nameMatch || !isValidXmlName(nameMatch[1]) || !areValidXmlAttributes(content.slice(nameMatch[0].length))) return false;
|
|
1579
|
+
seenElement = true;
|
|
1580
|
+
if (!selfClosing) stack.push(nameMatch[1]);
|
|
1581
|
+
position = close + 1;
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
return stack.length === 0 && seenElement;
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
function findXmlTagEnd(source, start) {
|
|
1588
|
+
let quote = null;
|
|
1589
|
+
for (let i = start; i < source.length; i += 1) {
|
|
1590
|
+
const ch = source[i];
|
|
1591
|
+
if (quote) {
|
|
1592
|
+
if (ch === quote) quote = null;
|
|
1593
|
+
} else if (ch === '"' || ch === "'") {
|
|
1594
|
+
quote = ch;
|
|
1595
|
+
} else if (ch === '>') {
|
|
1596
|
+
return i;
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
return -1;
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
function areValidXmlAttributes(source) {
|
|
1603
|
+
let rest = String(source).trim();
|
|
1604
|
+
const seen = new Set();
|
|
1605
|
+
while (rest) {
|
|
1606
|
+
const m = /^([A-Za-z_:\u0080-\uFFFF][A-Za-z0-9_.:\-\u0080-\uFFFF]*)\s*=\s*("[^"]*"|'[^']*')\s*/u.exec(rest);
|
|
1607
|
+
if (!m || seen.has(m[1]) || !isValidXmlAttributeValue(m[2].slice(1, -1))) return false;
|
|
1608
|
+
seen.add(m[1]);
|
|
1609
|
+
rest = rest.slice(m[0].length).trim();
|
|
1610
|
+
}
|
|
1611
|
+
return true;
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
function isValidXmlAttributeValue(value) {
|
|
1615
|
+
return !/[<]/.test(value) && isValidXmlText(value);
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
function isValidXmlText(value) {
|
|
1619
|
+
return !/[<&]/.test(String(value).replace(/&(?:amp|lt|gt|quot|apos|#\d+|#x[0-9A-Fa-f]+);/g, ''));
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
function isValidXmlName(name) {
|
|
1623
|
+
return /^[A-Za-z_:\u0080-\uFFFF][A-Za-z0-9_.:\-\u0080-\uFFFF]*$/u.test(name) && !/^xml$/i.test(name);
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1433
1626
|
function isLeapYearBigInt(year) {
|
|
1434
1627
|
if (year % 400n === 0n) return true;
|
|
1435
1628
|
if (year % 100n === 0n) return false;
|
|
@@ -1586,11 +1779,33 @@ function parseLangStringDatatypeValue(t, lexical) {
|
|
|
1586
1779
|
};
|
|
1587
1780
|
}
|
|
1588
1781
|
|
|
1782
|
+
function literalTermDatatypeKey(t) {
|
|
1783
|
+
return `${literalLexicalValue(t.value)}^^${literalDatatypeIri(t) || ''}`;
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1589
1786
|
function parseDatatypeValueForDatatype(t, dt) {
|
|
1590
1787
|
if (!(t instanceof Literal) || typeof dt !== 'string' || !isSupportedDatatypeIri(dt)) return null;
|
|
1788
|
+
|
|
1789
|
+
if (dt === RDFS_LITERAL_DT) {
|
|
1790
|
+
const ownDt = literalDatatypeIri(t);
|
|
1791
|
+
if (ownDt === null || !isSupportedDatatypeIri(ownDt)) return null;
|
|
1792
|
+
const ownValue = parseDatatypeValueForDatatype(t, ownDt);
|
|
1793
|
+
if (!ownValue) return null;
|
|
1794
|
+
return {
|
|
1795
|
+
family: 'literal',
|
|
1796
|
+
dt: RDFS_LITERAL_DT,
|
|
1797
|
+
value: literalTermDatatypeKey(t),
|
|
1798
|
+
canonicalLex: literalLexicalValue(t.value),
|
|
1799
|
+
canonicalLiteral: t,
|
|
1800
|
+
comparable: false,
|
|
1801
|
+
};
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1591
1804
|
const lexical = literalLexicalValue(t.value);
|
|
1592
1805
|
|
|
1593
1806
|
if (dt === RDF_LANGSTRING_DT) return parseLangStringDatatypeValue(t, lexical);
|
|
1807
|
+
if (dt === RDF_PLAIN_LITERAL_DT) return parsePlainLiteralDatatypeValue(lexical);
|
|
1808
|
+
if (dt === RDF_XML_LITERAL_DT) return parseXmlLiteralDatatypeValue(lexical);
|
|
1594
1809
|
if (literalHasLangTag(t.value)) return null;
|
|
1595
1810
|
if (XSD_STRING_LIKE_DTS.has(dt)) return parseStringLikeDatatypeValue(lexical, dt);
|
|
1596
1811
|
if (dt === XSD_BOOLEAN_DT) return parseBooleanDatatypeValue(lexical);
|
|
@@ -1616,10 +1831,11 @@ function compareDecimalExactValues(a, b) {
|
|
|
1616
1831
|
|
|
1617
1832
|
function datatypeValuesSame(a, b) {
|
|
1618
1833
|
if (!a || !b || !a.comparable || !b.comparable) return false;
|
|
1619
|
-
if (a.family === 'langString' || b.family === 'langString') {
|
|
1834
|
+
if (a.family === 'langString' || b.family === 'langString' || a.family === 'plainLiteral' || b.family === 'plainLiteral') {
|
|
1620
1835
|
return a.family === b.family && a.value === b.value && a.lang === b.lang;
|
|
1621
1836
|
}
|
|
1622
1837
|
if (a.family === 'string' && b.family === 'string') return a.value === b.value;
|
|
1838
|
+
if (a.family === 'xmlLiteral' && b.family === 'xmlLiteral') return a.value === b.value;
|
|
1623
1839
|
if (a.family === 'boolean' && b.family === 'boolean') return a.value === b.value;
|
|
1624
1840
|
if (a.family === 'anyURI' && b.family === 'anyURI') return a.value === b.value;
|
|
1625
1841
|
if (a.family === 'binary' && b.family === 'binary') {
|
|
@@ -1646,7 +1862,9 @@ function datatypeValuesSame(a, b) {
|
|
|
1646
1862
|
function datatypeValuesComparable(a, b) {
|
|
1647
1863
|
if (!a || !b || !a.comparable || !b.comparable) return false;
|
|
1648
1864
|
if (a.family === 'dateTime' && b.family === 'dateTime' && a.hasTimezone !== b.hasTimezone) return false;
|
|
1865
|
+
if (a.family === 'numeric' && b.family === 'numeric') return true;
|
|
1649
1866
|
if (a.family === 'langString' || b.family === 'langString') return a.family === b.family;
|
|
1867
|
+
if (a.family === 'plainLiteral' || b.family === 'plainLiteral') return a.family === b.family;
|
|
1650
1868
|
if (a.family === 'string' || b.family === 'string') return a.family === b.family;
|
|
1651
1869
|
return a.family === b.family;
|
|
1652
1870
|
}
|
|
@@ -1670,6 +1888,17 @@ function evalDatatypeInspectionBuiltin(g, subst, kind) {
|
|
|
1670
1888
|
const dt = literalDatatypeIri(g.s);
|
|
1671
1889
|
if (dt === null) return [];
|
|
1672
1890
|
valueTerm = internIri(dt);
|
|
1891
|
+
const out = evalBindBuiltinObject(g.o, valueTerm, subst);
|
|
1892
|
+
|
|
1893
|
+
// OWL 2 RL datatype rules often need common string literals to also
|
|
1894
|
+
// participate in generic literal comparisons, while application rules still
|
|
1895
|
+
// ask for their precise datatype explicitly. When the datatype output is
|
|
1896
|
+
// unbound, expose rdfs:Literal as an additional super-datatype answer for
|
|
1897
|
+
// xsd:string and rdf:langString; bound-object calls remain strictly exact.
|
|
1898
|
+
if (g.o instanceof Var && (dt === XSD_STRING_DT || dt === RDF_LANGSTRING_DT)) {
|
|
1899
|
+
out.push(...evalBindBuiltinObject(g.o, internIri(RDFS_LITERAL_DT), subst));
|
|
1900
|
+
}
|
|
1901
|
+
return out;
|
|
1673
1902
|
} else if (kind === 'lexicalForm') {
|
|
1674
1903
|
valueTerm = makeStringLiteral(literalLexicalValue(g.s.value));
|
|
1675
1904
|
} else if (kind === 'language') {
|
|
@@ -1738,6 +1967,57 @@ function evalDatatypeValueComparisonBuiltin(g, subst, same) {
|
|
|
1738
1967
|
return [];
|
|
1739
1968
|
}
|
|
1740
1969
|
|
|
1970
|
+
function evalOwlSameAsBuiltin(g, subst, facts, maxResults) {
|
|
1971
|
+
const out = [];
|
|
1972
|
+
const seen = new Set();
|
|
1973
|
+
const limit = typeof maxResults === 'number' && maxResults > 0 ? maxResults : Infinity;
|
|
1974
|
+
|
|
1975
|
+
function addPair(left, right) {
|
|
1976
|
+
if (out.length >= limit) return true;
|
|
1977
|
+
const key = `${termFastKey ? termFastKey(left) : termToN3(left)} ${termFastKey ? termFastKey(right) : termToN3(right)}`;
|
|
1978
|
+
if (seen.has(key)) return false;
|
|
1979
|
+
let s2 = unifyTerm(g.s, left, subst);
|
|
1980
|
+
if (s2 === null) return false;
|
|
1981
|
+
s2 = unifyTerm(g.o, right, s2);
|
|
1982
|
+
if (s2 === null) return false;
|
|
1983
|
+
seen.add(key);
|
|
1984
|
+
out.push(s2);
|
|
1985
|
+
return out.length >= limit;
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1988
|
+
function addReflexive(term) {
|
|
1989
|
+
return addPair(term, term);
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1992
|
+
const sameAsIri = OWL_NS + 'sameAs';
|
|
1993
|
+
const differentFromIri = OWL_NS + 'differentFrom';
|
|
1994
|
+
|
|
1995
|
+
for (const tr of facts || []) {
|
|
1996
|
+
if (tr && tr.p instanceof Iri && tr.p.value === sameAsIri) {
|
|
1997
|
+
if (addPair(tr.s, tr.o)) return out;
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
// owl:sameAs is reflexive. Avoid enumerating the entire active term universe
|
|
2002
|
+
// for fully-unbound goals; only add the reflexive pairs that are immediately
|
|
2003
|
+
// needed to detect explicit self-diversity (?x owl:differentFrom ?x).
|
|
2004
|
+
if (!(g.s instanceof Var) && !(g.o instanceof Var)) {
|
|
2005
|
+
if (termsEqual(g.s, g.o)) addReflexive(g.s);
|
|
2006
|
+
} else if (!(g.s instanceof Var)) {
|
|
2007
|
+
addReflexive(g.s);
|
|
2008
|
+
} else if (!(g.o instanceof Var)) {
|
|
2009
|
+
addReflexive(g.o);
|
|
2010
|
+
} else {
|
|
2011
|
+
for (const tr of facts || []) {
|
|
2012
|
+
if (tr && tr.p instanceof Iri && tr.p.value === differentFromIri && termsEqual(tr.s, tr.o)) {
|
|
2013
|
+
if (addReflexive(tr.s)) return out;
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
|
|
2018
|
+
return out;
|
|
2019
|
+
}
|
|
2020
|
+
|
|
1741
2021
|
// ===========================================================================
|
|
1742
2022
|
// Math builtin helpers
|
|
1743
2023
|
// ===========================================================================
|
|
@@ -2877,6 +3157,8 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
2877
3157
|
const pv = iriValue(g.p);
|
|
2878
3158
|
if (pv === null) return null;
|
|
2879
3159
|
|
|
3160
|
+
if (pv === OWL_NS + 'sameAs') return evalOwlSameAsBuiltin(g, subst, facts, maxResults);
|
|
3161
|
+
|
|
2880
3162
|
// Super restricted mode: disable *all* builtins except => / <= (log:implies / log:impliedBy)
|
|
2881
3163
|
if (typeof getSuperRestrictedMode === 'function' && getSuperRestrictedMode()) {
|
|
2882
3164
|
const allow1 = LOG_NS + 'implies';
|
|
@@ -5219,6 +5501,7 @@ function isBuiltinPred(p) {
|
|
|
5219
5501
|
}
|
|
5220
5502
|
|
|
5221
5503
|
if (__customBuiltinHandlers.has(v)) return true;
|
|
5504
|
+
if (v === OWL_NS + 'sameAs') return true;
|
|
5222
5505
|
|
|
5223
5506
|
return (
|
|
5224
5507
|
v.startsWith(CRYPTO_NS) ||
|
package/eyeling-builtins.ttl
CHANGED
|
@@ -54,19 +54,19 @@ dt:language a ex:Builtin ; ex:kind ex:Function ;
|
|
|
54
54
|
rdfs:comment "Extracts the language tag of an rdf:langString literal as a string literal." .
|
|
55
55
|
|
|
56
56
|
dt:validForDatatype a ex:Builtin ; ex:kind ex:Test ;
|
|
57
|
-
rdfs:comment "Succeeds iff the subject literal has a lexical form accepted by the object datatype and denotes a value in that datatype's value space. Also supports tuple-to-boolean form: (literal datatype) dt:validForDatatype true/false. Supports OWL 2 RL-relevant XSD strings, booleans, numerics, binary types, anyURI, dateTime, and dateTimeStamp." .
|
|
57
|
+
rdfs:comment "Succeeds iff the subject literal has a lexical form accepted by the object datatype and denotes a value in that datatype's value space. Also supports tuple-to-boolean form: (literal datatype) dt:validForDatatype true/false. Supports OWL 2 RL-relevant RDF and XSD datatypes, including rdf:PlainLiteral, rdf:XMLLiteral, rdf:langString, rdfs:Literal, strings, booleans, numerics, binary types, anyURI, dateTime, and dateTimeStamp." .
|
|
58
58
|
|
|
59
59
|
dt:invalidForDatatype a ex:Builtin ; ex:kind ex:Test ;
|
|
60
60
|
rdfs:comment "Succeeds iff the object datatype is supported and the subject literal is not valid for it. Also supports tuple-to-boolean form: (literal datatype) dt:invalidForDatatype true/false." .
|
|
61
61
|
|
|
62
62
|
dt:sameValueAs a ex:Builtin ; ex:kind ex:Test ;
|
|
63
|
-
rdfs:comment "Value-space equality over supported datatypes, including integer/decimal numeric equality, boolean 1/true equality, timezone-normalized dateTime equality with exact midnight rollover,
|
|
63
|
+
rdfs:comment "Value-space equality over supported datatypes, including integer/decimal numeric equality, boolean 1/true equality, rdf:PlainLiteral language-tag normalization, rdf:XMLLiteral lexical comparison after XML well-formedness checks, timezone-normalized dateTime equality with exact midnight rollover, and binary byte equality." .
|
|
64
64
|
|
|
65
65
|
dt:differentValueFrom a ex:Builtin ; ex:kind ex:Test ;
|
|
66
66
|
rdfs:comment "Value-space inequality over supported and comparable datatype values." .
|
|
67
67
|
|
|
68
68
|
dt:canonicalLiteral a ex:Builtin ; ex:kind ex:Function ;
|
|
69
|
-
rdfs:comment "Binds/unifies the object with a canonical literal for the subject literal's datatype value, such as normalized integer, boolean,
|
|
69
|
+
rdfs:comment "Binds/unifies the object with a canonical literal for the subject literal's datatype value, such as normalized integer, boolean, rdf:PlainLiteral language tags, rdf:XMLLiteral fragments, binary, and timezone-normalized dateTime lexicals, including 24:00:00 rollover." .
|
|
70
70
|
|
|
71
71
|
# --- crypto: ---------------------------------------------------------
|
|
72
72
|
|