eyeling 1.31.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/eyeling.js CHANGED
@@ -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
- const lastQuote = litVal.lastIndexOf('"');
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 + 1;
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 (!/^[A-Za-z]+(?:-[A-Za-z0-9]+)*(?:--(?:ltr|rtl))?$/.test(tag)) return null;
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
- let value = String(lexical);
1252
+ const value = String(lexical);
1222
1253
 
1223
1254
  if (dt === XSD_NS + 'normalizedString') {
1224
- value = value.replace(/[\t\n\r]/g, ' ');
1255
+ if (/[\t\n\r]/.test(value)) return null;
1225
1256
  } else if (dt !== XSD_STRING_DT) {
1226
- value = value.replace(/[\t\n\r ]+/g, ' ').trim();
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 (!/^[A-Za-z]{1,8}(?:-[A-Za-z0-9]{1,8})*$/.test(value)) return null;
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 (/[\u0000-\u001F\u007F]/.test(value)) return null;
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) ||
@@ -16909,7 +17192,8 @@ function termEquals(self, other) {
16909
17192
  !!self.datatype &&
16910
17193
  typeof self.datatype.equals === 'function' &&
16911
17194
  self.datatype.equals(other.datatype) &&
16912
- self.language === (other.language || '')
17195
+ self.language === (other.language || '') &&
17196
+ (self.direction || '') === (other.direction || '')
16913
17197
  );
16914
17198
  }
16915
17199
 
@@ -16974,6 +17258,7 @@ class Literal {
16974
17258
  this.termType = 'Literal';
16975
17259
  this.value = String(value);
16976
17260
  this.language = '';
17261
+ this.direction = '';
16977
17262
  this.datatype = null;
16978
17263
 
16979
17264
  if (typeof languageOrDatatype === 'string') {
@@ -16981,6 +17266,10 @@ class Literal {
16981
17266
  this.datatype = new NamedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#langString');
16982
17267
  } else if (isRdfJsTerm(languageOrDatatype)) {
16983
17268
  this.datatype = languageOrDatatype;
17269
+ } else if (languageOrDatatype && typeof languageOrDatatype === 'object') {
17270
+ this.language = String(languageOrDatatype.language || '');
17271
+ this.direction = languageOrDatatype.direction || '';
17272
+ this.datatype = new NamedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#langString');
16984
17273
  } else {
16985
17274
  this.datatype = new NamedNode(XSD_NS + 'string');
16986
17275
  }
@@ -17027,6 +17316,38 @@ const dataFactory = {
17027
17316
  quad(subject, predicate, object, graph) {
17028
17317
  return new Quad(subject, predicate, object, graph || defaultGraphSingleton);
17029
17318
  },
17319
+ fromTerm(original) {
17320
+ if (!isRdfJsTerm(original)) throw new TypeError('Expected an RDF/JS term');
17321
+
17322
+ switch (original.termType) {
17323
+ case 'NamedNode':
17324
+ return new NamedNode(original.value);
17325
+ case 'BlankNode':
17326
+ return new BlankNode(original.value);
17327
+ case 'Literal':
17328
+ if (original.language || original.direction) {
17329
+ return new Literal(original.value, { language: original.language || '', direction: original.direction || '' });
17330
+ }
17331
+ return new Literal(original.value, original.datatype ? dataFactory.fromTerm(original.datatype) : undefined);
17332
+ case 'Variable':
17333
+ return new Variable(original.value);
17334
+ case 'DefaultGraph':
17335
+ return defaultGraphSingleton;
17336
+ case 'Quad':
17337
+ return dataFactory.fromQuad(original);
17338
+ default:
17339
+ throw new TypeError(`Unsupported RDF/JS termType ${JSON.stringify(original.termType)}`);
17340
+ }
17341
+ },
17342
+ fromQuad(original) {
17343
+ if (!isRdfJsQuad(original)) throw new TypeError('Expected an RDF/JS Quad');
17344
+ return new Quad(
17345
+ dataFactory.fromTerm(original.subject),
17346
+ dataFactory.fromTerm(original.predicate),
17347
+ dataFactory.fromTerm(original.object),
17348
+ dataFactory.fromTerm(original.graph),
17349
+ );
17350
+ },
17030
17351
  };
17031
17352
 
17032
17353
  function getDataFactory(factory) {