eyeling 1.10.20 → 1.10.21
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/HANDBOOK.md +88 -2
- package/examples/bind-builtins.n3 +11 -0
- package/examples/bind.n3 +7 -0
- package/examples/brussels-brew-club.n3 +119 -0
- package/examples/builtins-string-math.n3 +11 -0
- package/examples/builtins-triple-termtests.n3 +7 -0
- package/examples/family.n3 +10 -0
- package/examples/filter-demorgan.n3 +9 -0
- package/examples/filter-in-notin.n3 +10 -0
- package/examples/filter-nested-or.n3 +10 -0
- package/examples/filter.n3 +8 -0
- package/examples/input/bind-builtins.srl +30 -0
- package/examples/input/bind.srl +12 -0
- package/examples/input/builtins-string-math.srl +38 -0
- package/examples/input/builtins-triple-termtests.srl +27 -0
- package/examples/input/family.srl +12 -0
- package/examples/input/filter-demorgan.srl +15 -0
- package/examples/input/filter-in-notin.srl +15 -0
- package/examples/input/filter-nested-or.srl +15 -0
- package/examples/input/filter.srl +9 -0
- package/examples/input/snaf.srl +6 -0
- package/examples/json-pointer.n3 +75 -0
- package/examples/json-reconcile-vat.n3 +361 -0
- package/examples/output/bind-builtins.n3 +9 -0
- package/examples/output/bind.n3 +3 -0
- package/examples/output/brussels-brew-club.n3 +22 -0
- package/examples/output/builtins-string-math.n3 +0 -0
- package/examples/output/builtins-triple-termtests.n3 +0 -0
- package/examples/output/family.n3 +13 -0
- package/examples/output/filter-demorgan.n3 +3 -0
- package/examples/output/filter-in-notin.n3 +4 -0
- package/examples/output/filter-nested-or.n3 +4 -0
- package/examples/output/filter.n3 +3 -0
- package/examples/output/json-pointer.n3 +13 -0
- package/examples/output/json-reconcile-vat.n3 +226 -0
- package/examples/output/snaf.n3 +3 -0
- package/examples/snaf.n3 +6 -0
- package/eyeling-builtins.ttl +48 -0
- package/eyeling.js +312 -1
- package/lib/engine.js +307 -1
- package/lib/rules.js +5 -0
- package/package.json +1 -1
- package/test/n3gen.test.js +4 -4
- package/test/package.test.js +1 -1
- package/tools/n3gen.js +1883 -6
package/HANDBOOK.md
CHANGED
|
@@ -702,7 +702,7 @@ With that, we can tour the builtin families as Eyeling actually implements them.
|
|
|
702
702
|
|
|
703
703
|
These builtins hash a string and return a lowercase hex digest as a plain string literal.
|
|
704
704
|
|
|
705
|
-
### `crypto:sha`, `crypto:md5`, `crypto:sha256`, `crypto:sha512`
|
|
705
|
+
### `crypto:sha`, `crypto:md5`, `crypto:sha256`, `crypto:sha384`, `crypto:sha512`
|
|
706
706
|
|
|
707
707
|
**Shape:**
|
|
708
708
|
`$literal crypto:sha256 $digest`
|
|
@@ -1224,6 +1224,22 @@ Returns one of four IRIs:
|
|
|
1224
1224
|
* `rdf:List` (closed or open list terms)
|
|
1225
1225
|
* `log:Other` (IRIs, blank nodes, etc.)
|
|
1226
1226
|
|
|
1227
|
+
|
|
1228
|
+
|
|
1229
|
+
#### `log:isIRI` / `log:isLiteral` / `log:isBlank` / `log:isNumeric` / `log:isTriple` (extensions)
|
|
1230
|
+
|
|
1231
|
+
Convenience *test* builtins (useful for SHACL/Sparql-style mappings). They **succeed** when the test holds and typically use `true` as the object.
|
|
1232
|
+
|
|
1233
|
+
- **Shapes:**
|
|
1234
|
+
- `$t+ log:isIRI true .`
|
|
1235
|
+
- `$t+ log:isLiteral true .`
|
|
1236
|
+
- `$t+ log:isBlank true .`
|
|
1237
|
+
- `$t+ log:isNumeric true .`
|
|
1238
|
+
- `$t+ log:isTriple true .` (formula with exactly one triple)
|
|
1239
|
+
|
|
1240
|
+
These are treated as **constraints** for rule-body ordering.
|
|
1241
|
+
|
|
1242
|
+
|
|
1227
1243
|
### Literal constructors
|
|
1228
1244
|
|
|
1229
1245
|
These two are classic N3 “bridge” operators between structured data and concrete RDF literal forms.
|
|
@@ -1321,6 +1337,19 @@ This is treated as a constraint builtin.
|
|
|
1321
1337
|
|
|
1322
1338
|
Deterministically maps a *ground* term to a Skolem IRI in Eyeling’s well-known namespace. This is extremely useful when you want a repeatable identifier derived from structured content.
|
|
1323
1339
|
|
|
1340
|
+
|
|
1341
|
+
|
|
1342
|
+
#### `log:uuid` / `log:struuid` (extensions)
|
|
1343
|
+
|
|
1344
|
+
Generate fresh UUID values.
|
|
1345
|
+
|
|
1346
|
+
- **Shapes:**
|
|
1347
|
+
- `"" log:uuid ?u .` binds `?u` to an IRI like `<urn:uuid:...>`.
|
|
1348
|
+
- `"" log:struuid ?s .` binds `?s` to a UUID string literal.
|
|
1349
|
+
|
|
1350
|
+
**Warning:** These are non-deterministic and can affect termination; prefer `log:skolem` for deterministic identifiers.
|
|
1351
|
+
|
|
1352
|
+
|
|
1324
1353
|
#### `log:uri`
|
|
1325
1354
|
|
|
1326
1355
|
Bidirectional conversion between IRIs and their string form:
|
|
@@ -1354,7 +1383,7 @@ This is treated as a constraint builtin (it shouldn’t drive search; it should
|
|
|
1354
1383
|
|
|
1355
1384
|
---
|
|
1356
1385
|
|
|
1357
|
-
## 11.3.6 `string:` — string casting, tests, and
|
|
1386
|
+
## 11.3.6 `string:` — string casting, tests, regexes, and JSON pointers
|
|
1358
1387
|
|
|
1359
1388
|
Eyeling implements string builtins with a deliberate interpretation of “domain is `xsd:string`”:
|
|
1360
1389
|
|
|
@@ -1385,6 +1414,35 @@ A tiny `sprintf` subset:
|
|
|
1385
1414
|
* Any other specifier (`%d`, `%f`, …) causes the builtin to fail.
|
|
1386
1415
|
* Missing arguments are treated as empty strings.
|
|
1387
1416
|
|
|
1417
|
+
|
|
1418
|
+
|
|
1419
|
+
#### `string:length` (extension)
|
|
1420
|
+
|
|
1421
|
+
Length of the subject string, like SPARQL `STRLEN`.
|
|
1422
|
+
|
|
1423
|
+
- **Shape:** `$s+ string:length $o-`
|
|
1424
|
+
- **Output:** `$o` is unified/bound to an integer token.
|
|
1425
|
+
|
|
1426
|
+
#### `string:substring` (extension)
|
|
1427
|
+
|
|
1428
|
+
Substring, like SPARQL `SUBSTR`.
|
|
1429
|
+
|
|
1430
|
+
- **Shape:** `( $s+ $start+ [$len+] ) string:substring $o-`
|
|
1431
|
+
- **Notes:** `start` is **1-based**. `len` is optional; if omitted, the substring runs to the end.
|
|
1432
|
+
|
|
1433
|
+
#### `string:upperCase` / `string:lowerCase` (extension)
|
|
1434
|
+
|
|
1435
|
+
Case conversion, like SPARQL `UCASE` / `LCASE`.
|
|
1436
|
+
|
|
1437
|
+
- **Shape:** `$s+ string:upperCase $o-` and `$s+ string:lowerCase $o-`
|
|
1438
|
+
|
|
1439
|
+
#### `string:encodeForURI` (extension)
|
|
1440
|
+
|
|
1441
|
+
Percent-encode a string, like SPARQL `ENCODE_FOR_URI`.
|
|
1442
|
+
|
|
1443
|
+
- **Shape:** `$s+ string:encodeForURI $o-`
|
|
1444
|
+
|
|
1445
|
+
|
|
1388
1446
|
### Containment and prefix/suffix tests (constraints)
|
|
1389
1447
|
|
|
1390
1448
|
* `string:contains`
|
|
@@ -1438,6 +1496,34 @@ Tests whether `pattern` matches `data`.
|
|
|
1438
1496
|
|
|
1439
1497
|
Matches the regex once and returns the **first capturing group** (group 1). If there is no match or no group, it fails.
|
|
1440
1498
|
|
|
1499
|
+
### JSON pointer lookup
|
|
1500
|
+
|
|
1501
|
+
#### `string:jsonPointer`
|
|
1502
|
+
|
|
1503
|
+
**Shape:**
|
|
1504
|
+
`( jsonText pointer ) string:jsonPointer value`
|
|
1505
|
+
|
|
1506
|
+
This builtin is intentionally “bridgey”: it lets you reach into JSON and get back an RDF/N3 term.
|
|
1507
|
+
|
|
1508
|
+
Rules:
|
|
1509
|
+
|
|
1510
|
+
* `jsonText` must be an `rdf:JSON` literal (Eyeling is permissive and may accept a couple of equivalent datatype spellings).
|
|
1511
|
+
* `pointer` is a string; Eyeling supports:
|
|
1512
|
+
|
|
1513
|
+
* standard RFC 6901 pointers like `/a/b/0`
|
|
1514
|
+
* URI fragment form like `#/a/b` (it is decoded first)
|
|
1515
|
+
* The JSON is parsed and cached; pointer results are cached per `(jsonText, pointer)`.
|
|
1516
|
+
|
|
1517
|
+
Returned terms follow Eyeling’s `jsonToTerm` mapping:
|
|
1518
|
+
|
|
1519
|
+
* JSON `null` → `"null"` (a plain string literal)
|
|
1520
|
+
* JSON string → plain string literal
|
|
1521
|
+
* JSON number → numeric token literal (untyped)
|
|
1522
|
+
* JSON boolean → `true` / `false` token literal (untyped boolean token)
|
|
1523
|
+
* JSON array → an N3 list term whose elements are recursively converted
|
|
1524
|
+
* JSON object → an `rdf:JSON` literal containing the object’s JSON text
|
|
1525
|
+
|
|
1526
|
+
This design keeps the builtin total and predictable even for nested structures.
|
|
1441
1527
|
|
|
1442
1528
|
## 11.4 `log:outputString` as a controlled side effect
|
|
1443
1529
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
@prefix : <http://example/#> .
|
|
2
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
3
|
+
@prefix string: <http://www.w3.org/2000/10/swap/string#> .
|
|
4
|
+
@prefix time: <http://www.w3.org/2000/10/swap/time#> .
|
|
5
|
+
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
6
|
+
@prefix math: <http://www.w3.org/2000/10/swap/math#> .
|
|
7
|
+
@prefix crypto: <http://www.w3.org/2000/10/swap/crypto#> .
|
|
8
|
+
|
|
9
|
+
:p1 :givenName "Ada" ; :familyName "Lovelace" ; :born "1815-12-10T00:00:00Z"^^xsd:dateTime .
|
|
10
|
+
|
|
11
|
+
{ ?p :givenName ?g . ?p :familyName ?f . ?p :born ?d . (?g " " ?f) string:concatenation ?full . (?full " " "_") string:replace ?lbl . (?lbl "en") log:langlit ?lblEn . ?lbl crypto:sha ?h . ?d time:year ?y . (?y 1) math:sum ?__e1 . (?__e1 2) math:product ?z . ("42" xsd:integer) log:dtlit ?i . } => { ?p :label ?lbl ; :labelEn ?lblEn ; :hash ?h ; :birthYear ?y ; :calc ?z ; :intLit ?i . } .
|
package/examples/bind.n3
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
@prefix : <http://example/#> .
|
|
2
|
+
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
3
|
+
@prefix string: <http://www.w3.org/2000/10/swap/string#> .
|
|
4
|
+
|
|
5
|
+
:phayes a :Person ; :givenName "Pat"; :familyName "Hayes" .
|
|
6
|
+
|
|
7
|
+
{ ?x a :Person . ?SCOPE log:notIncludes { ?x :name ?someName . } . ?x :givenName ?name1 . ?x :familyName ?name2 . (?name1 " " ?name2) string:concatenation ?FN . } => { ?x :name ?FN } .
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# ==================
|
|
2
|
+
# Brussels Brew Club
|
|
3
|
+
# ==================
|
|
4
|
+
#
|
|
5
|
+
# A tiny loyalty + compliance reasoner with:
|
|
6
|
+
# - JSON profile parsing (rdf:JSON + string:jsonPointer)
|
|
7
|
+
# - backward rules (<=) used during forward chaining
|
|
8
|
+
# - stable IDs with log:skolem
|
|
9
|
+
# - timestamps with time:localTime
|
|
10
|
+
|
|
11
|
+
@prefix : <http://example.org/brewclub#> .
|
|
12
|
+
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
|
13
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
14
|
+
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
15
|
+
@prefix math: <http://www.w3.org/2000/10/swap/math#> .
|
|
16
|
+
@prefix string:<http://www.w3.org/2000/10/swap/string#> .
|
|
17
|
+
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
|
18
|
+
|
|
19
|
+
# ---------------------------
|
|
20
|
+
# Deterministic "run context"
|
|
21
|
+
# ---------------------------
|
|
22
|
+
|
|
23
|
+
:run :now "2000-01-01T00:00:00+00:00"^^xsd:dateTime .
|
|
24
|
+
|
|
25
|
+
# -----
|
|
26
|
+
# Facts
|
|
27
|
+
# -----
|
|
28
|
+
|
|
29
|
+
:alice :profileJson """{ "name": "Alice", "age": 23, "city": "Brussels" }"""^^rdf:JSON .
|
|
30
|
+
:bob :profileJson """{ "name": "Bob", "age": 16, "city": "Brussels" }"""^^rdf:JSON .
|
|
31
|
+
|
|
32
|
+
:order1 a :Order ;
|
|
33
|
+
:id "ORD-2025-001" ;
|
|
34
|
+
:by :alice ;
|
|
35
|
+
:total "31.50"^^xsd:decimal ;
|
|
36
|
+
:contains :Espresso, :Croissant .
|
|
37
|
+
|
|
38
|
+
:order2 a :Order ;
|
|
39
|
+
:id "ORD-2025-002" ;
|
|
40
|
+
:by :bob ;
|
|
41
|
+
:total "12.00"^^xsd:decimal ;
|
|
42
|
+
:contains :HotChocolate .
|
|
43
|
+
|
|
44
|
+
:Beer a :AlcoholicDrink .
|
|
45
|
+
:Espresso a :Drink .
|
|
46
|
+
:HotChocolate a :Drink .
|
|
47
|
+
:Croissant a :Food .
|
|
48
|
+
|
|
49
|
+
# -----
|
|
50
|
+
# Rules
|
|
51
|
+
# -----
|
|
52
|
+
|
|
53
|
+
# 1) Extract fields from JSON profile into normal triples
|
|
54
|
+
{
|
|
55
|
+
?p :profileJson ?j .
|
|
56
|
+
(?j "/age") string:jsonPointer ?age .
|
|
57
|
+
(?j "/name") string:jsonPointer ?name .
|
|
58
|
+
(?j "/city") string:jsonPointer ?city .
|
|
59
|
+
}
|
|
60
|
+
=>
|
|
61
|
+
{
|
|
62
|
+
?p :age ?age ;
|
|
63
|
+
:name ?name ;
|
|
64
|
+
:city ?city .
|
|
65
|
+
}.
|
|
66
|
+
|
|
67
|
+
# 2) Backward rule: Adult is defined by age > 17
|
|
68
|
+
{ ?p a :Adult }
|
|
69
|
+
<=
|
|
70
|
+
{ ?p :age ?a . ?a math:greaterThan 17 }.
|
|
71
|
+
|
|
72
|
+
# 3) Backward rule: BigOrder is defined by total > 25.00
|
|
73
|
+
{ ?o a :BigOrder }
|
|
74
|
+
<=
|
|
75
|
+
{ ?o :total ?t . ?t math:greaterThan "25.00"^^xsd:decimal }.
|
|
76
|
+
|
|
77
|
+
# 4) Deterministic membership card rule:
|
|
78
|
+
# Uses :run :now ?now instead of time:localTime ?now
|
|
79
|
+
{
|
|
80
|
+
?p a :Adult .
|
|
81
|
+
?p :name ?name .
|
|
82
|
+
:run :now ?now .
|
|
83
|
+
|
|
84
|
+
(?p) log:skolem ?card .
|
|
85
|
+
("Brew Club card for " ?name) string:concatenation ?label .
|
|
86
|
+
}
|
|
87
|
+
=>
|
|
88
|
+
{
|
|
89
|
+
?card a :MembershipCard ;
|
|
90
|
+
:holder ?p ;
|
|
91
|
+
:issuedAt ?now ;
|
|
92
|
+
rdfs:label ?label .
|
|
93
|
+
}.
|
|
94
|
+
|
|
95
|
+
# 5) Discounts: BigOrder + Adult -> discount eligibility
|
|
96
|
+
{
|
|
97
|
+
?o a :Order .
|
|
98
|
+
?o a :BigOrder .
|
|
99
|
+
?o :by ?p .
|
|
100
|
+
?p a :Adult .
|
|
101
|
+
}
|
|
102
|
+
=>
|
|
103
|
+
{
|
|
104
|
+
?o :eligibleDiscount "true"^^xsd:boolean .
|
|
105
|
+
}.
|
|
106
|
+
|
|
107
|
+
# 6) Stable receipt IRI from the order id
|
|
108
|
+
{
|
|
109
|
+
?o a :Order .
|
|
110
|
+
?o :id ?id .
|
|
111
|
+
(?id) log:skolem ?receipt .
|
|
112
|
+
}
|
|
113
|
+
=>
|
|
114
|
+
{
|
|
115
|
+
?receipt a :Receipt ;
|
|
116
|
+
:forOrder ?o ;
|
|
117
|
+
:receiptId ?id .
|
|
118
|
+
}.
|
|
119
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
@prefix : <http://example/#> .
|
|
2
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
3
|
+
@prefix string: <http://www.w3.org/2000/10/swap/string#> .
|
|
4
|
+
@prefix time: <http://www.w3.org/2000/10/swap/time#> .
|
|
5
|
+
@prefix math: <http://www.w3.org/2000/10/swap/math#> .
|
|
6
|
+
|
|
7
|
+
:x :num 1.2 ;
|
|
8
|
+
:txt "Hello World" ;
|
|
9
|
+
:dt "2020-01-01T13:14:15Z"^^xsd:dateTime .
|
|
10
|
+
|
|
11
|
+
{ :x :num ?v . :x :txt ?s . :x :dt ?dt . ?v math:ceiling ?c . ?v math:floor ?f . (?s 1 5) string:substring ?sub . ?s string:length ?len . ?s string:upperCase ?up . ?s string:lowerCase ?lo . ?s string:encodeForURI ?enc . ?dt time:hour ?h . } => { :x :ceil ?c ; :floor ?f ; :substr ?sub ; :strlen ?len ; :ucase ?up ; :lcase ?lo ; :encoded ?enc ; :hours ?h . } .
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
@prefix : <http://example/#> .
|
|
2
|
+
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
3
|
+
|
|
4
|
+
:alice :knows :bob .
|
|
5
|
+
:bob :age 42 .
|
|
6
|
+
|
|
7
|
+
{ :alice :knows :bob . ?t log:isTriple true . ?s log:isIRI true . ?t log:equalTo { :alice :knows :bob . } . ?t log:includes { ?s ?__e2 ?__e3 . } . ?t log:includes { ?__e4 ?p ?__e6 . } . ?t log:includes { ?__e7 ?__e8 ?o . } . } => { :test :triple ?t ; :subject ?s ; :predicate ?p ; :object ?o ; :subjectIsIRI true ; :tripleOk true . } .
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
@prefix : <http://example.org/#> .
|
|
2
|
+
|
|
3
|
+
:A :fatherOf :X . :B :motherOf :X . :C :motherOf :A .
|
|
4
|
+
|
|
5
|
+
{ ?y :fatherOf ?x . } => { ?x :childOf ?y } .
|
|
6
|
+
{ ?y :motherOf ?x . } => { ?x :childOf ?y } .
|
|
7
|
+
{ ?x :childOf ?y . } => { ?x :descendedFrom ?y } .
|
|
8
|
+
{ ?x :childOf ?z . ?z :childOf ?y . } => { ?x :descendedFrom ?y } .
|
|
9
|
+
{ ?y :descendedFrom ?x . } => { ?x :ancestorOf ?y } .
|
|
10
|
+
{ ?a :ancestorOf ?c . ?c :ancestorOf ?b . } => { ?a :ancestorOf ?b } .
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
@prefix : <http://example/#> .
|
|
2
|
+
@prefix string: <http://www.w3.org/2000/10/swap/string#> .
|
|
3
|
+
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
4
|
+
|
|
5
|
+
:alice :name "Alice" .
|
|
6
|
+
:bob :name "Bob" .
|
|
7
|
+
:eve :name "Eve" .
|
|
8
|
+
|
|
9
|
+
{ ?p :name ?n . ?SCOPE log:notIncludes { ?n string:contains "o" . } . ?SCOPE log:notIncludes { ?n string:startsWith "E" . } . } => { ?p :passesNameFilter true . } .
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
@prefix : <http://example/#> .
|
|
2
|
+
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
3
|
+
@prefix list: <http://www.w3.org/2000/10/swap/list#> .
|
|
4
|
+
|
|
5
|
+
:a :score 1 .
|
|
6
|
+
:b :score 2 .
|
|
7
|
+
:c :score 3 .
|
|
8
|
+
:d :score 4 .
|
|
9
|
+
|
|
10
|
+
{ ?x :score ?s . ?s list:in (1 3 5) . ?SCOPE log:notIncludes { ?s list:in (2 4) . } . } => { ?x :isSpecial true . } .
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
@prefix : <http://example/#> .
|
|
2
|
+
@prefix math: <http://www.w3.org/2000/10/swap/math#> .
|
|
3
|
+
@prefix string: <http://www.w3.org/2000/10/swap/string#> .
|
|
4
|
+
|
|
5
|
+
:bob :age 20 ; :name "Bob" .
|
|
6
|
+
:alice :age 15 ; :name "Alice" .
|
|
7
|
+
:bea :age 22 ; :name "Bea" .
|
|
8
|
+
|
|
9
|
+
{ ?p :age ?age . ?p :name ?name . ?age math:notLessThan 18 . ?name string:startsWith "A" . } => { ?p :adultNamedByRule true . } .
|
|
10
|
+
{ ?p :age ?age . ?p :name ?name . ?age math:notLessThan 18 . ?name string:matches "^B" . } => { ?p :adultNamedByRule true . } .
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
@prefix : <http://example/#> .
|
|
2
|
+
@prefix math: <http://www.w3.org/2000/10/swap/math#> .
|
|
3
|
+
|
|
4
|
+
:x :p 1 ; :q 2 .
|
|
5
|
+
|
|
6
|
+
{ ?x :p ?v1 . ?x :q ?v2 . ?v1 math:greaterThan 0 . ?v2 math:greaterThan 0 . } => { ?x :bothPositive true . } .
|
|
7
|
+
{ ?x :p ?v1 . ?x :q ?v2 . ?v1 math:equalTo 0 . } => { ?x :oneIsZero true . } .
|
|
8
|
+
{ ?x :p ?v1 . ?x :q ?v2 . ?v2 math:equalTo 0 . } => { ?x :oneIsZero true . } .
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
PREFIX : <http://example/#>
|
|
2
|
+
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
|
|
3
|
+
|
|
4
|
+
DATA {
|
|
5
|
+
# NOTE: Eyeling's time:* extractors currently operate on xsd:dateTime (not xsd:date).
|
|
6
|
+
:p1 :givenName "Ada" ; :familyName "Lovelace" ; :born "1815-12-10T00:00:00Z"^^xsd:dateTime .
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
# Demonstrates BIND compilation to N3 builtins:
|
|
10
|
+
# - concat/replace (string:)
|
|
11
|
+
# - sha1 (crypto:)
|
|
12
|
+
# - year (time:)
|
|
13
|
+
# - arithmetic (+,*) (math:)
|
|
14
|
+
# - strlang/strdt (log:)
|
|
15
|
+
RULE {
|
|
16
|
+
?p :label ?lbl ; :labelEn ?lblEn ; :hash ?h ; :birthYear ?y ; :calc ?z ; :intLit ?i .
|
|
17
|
+
}
|
|
18
|
+
WHERE {
|
|
19
|
+
?p :givenName ?g ; :familyName ?f ; :born ?d .
|
|
20
|
+
|
|
21
|
+
BIND(concat(?g, " ", ?f) AS ?full)
|
|
22
|
+
BIND(replace(?full, " ", "_") AS ?lbl)
|
|
23
|
+
BIND(strlang(?lbl, "en") AS ?lblEn)
|
|
24
|
+
BIND(sha1(?lbl) AS ?h)
|
|
25
|
+
|
|
26
|
+
BIND(year(?d) AS ?y)
|
|
27
|
+
BIND((?y + 1) * 2 AS ?z)
|
|
28
|
+
|
|
29
|
+
BIND(strdt("42", xsd:integer) AS ?i)
|
|
30
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
PREFIX : <http://example/#>
|
|
2
|
+
|
|
3
|
+
DATA { :phayes a :Person ; :givenName "Pat"; :familyName "Hayes" . }
|
|
4
|
+
|
|
5
|
+
# Default value - calculate a name
|
|
6
|
+
RULE { ?x :name ?FN } WHERE {
|
|
7
|
+
?x a :Person
|
|
8
|
+
NOT { ?x :name ?someName }
|
|
9
|
+
?x :givenName ?name1 ;
|
|
10
|
+
:familyName ?name2 .
|
|
11
|
+
BIND(concat(?name1, " ", ?name2) AS ?FN)
|
|
12
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
PREFIX : <http://example/#>
|
|
2
|
+
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
|
|
3
|
+
|
|
4
|
+
DATA {
|
|
5
|
+
:x :num 1.2 ;
|
|
6
|
+
:txt "Hello World" ;
|
|
7
|
+
:dt "2020-01-01T13:14:15Z"^^xsd:dateTime .
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
RULE {
|
|
11
|
+
:x :ceil ?c ;
|
|
12
|
+
:floor ?f ;
|
|
13
|
+
:substr ?sub ;
|
|
14
|
+
:strlen ?len ;
|
|
15
|
+
:ucase ?up ;
|
|
16
|
+
:lcase ?lo ;
|
|
17
|
+
:encoded ?enc ;
|
|
18
|
+
:hours ?h .
|
|
19
|
+
}
|
|
20
|
+
WHERE {
|
|
21
|
+
:x :num ?v ;
|
|
22
|
+
:txt ?s ;
|
|
23
|
+
:dt ?dt .
|
|
24
|
+
|
|
25
|
+
BIND(CEIL(?v) AS ?c)
|
|
26
|
+
BIND(FLOOR(?v) AS ?f)
|
|
27
|
+
|
|
28
|
+
# SPARQL SUBSTR is 1-based; this should produce "Hello"
|
|
29
|
+
BIND(SUBSTR(?s, 1, 5) AS ?sub)
|
|
30
|
+
|
|
31
|
+
BIND(STRLEN(?s) AS ?len)
|
|
32
|
+
BIND(UCASE(?s) AS ?up)
|
|
33
|
+
BIND(LCASE(?s) AS ?lo)
|
|
34
|
+
BIND(ENCODE_FOR_URI(?s) AS ?enc)
|
|
35
|
+
|
|
36
|
+
BIND(HOURS(?dt) AS ?h)
|
|
37
|
+
}
|
|
38
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
PREFIX : <http://example/#>
|
|
2
|
+
|
|
3
|
+
DATA {
|
|
4
|
+
:alice :knows :bob .
|
|
5
|
+
:bob :age 42 .
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
RULE {
|
|
9
|
+
:test :triple ?t ;
|
|
10
|
+
:subject ?s ;
|
|
11
|
+
:predicate ?p ;
|
|
12
|
+
:object ?o ;
|
|
13
|
+
:subjectIsIRI true ;
|
|
14
|
+
:tripleOk true .
|
|
15
|
+
}
|
|
16
|
+
WHERE {
|
|
17
|
+
:alice :knows :bob .
|
|
18
|
+
|
|
19
|
+
BIND(TRIPLE(:alice, :knows, :bob) AS ?t)
|
|
20
|
+
BIND(SUBJECT(?t) AS ?s)
|
|
21
|
+
BIND(PREDICATE(?t) AS ?p)
|
|
22
|
+
BIND(OBJECT(?t) AS ?o)
|
|
23
|
+
|
|
24
|
+
# Term-test builtins (as in SPARQL)
|
|
25
|
+
FILTER ( isTRIPLE(?t) && isIRI(?s) )
|
|
26
|
+
}
|
|
27
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
PREFIX : <http://example.org/#>
|
|
2
|
+
|
|
3
|
+
DATA { :A :fatherOf :X . :B :motherOf :X . :C :motherOf :A . }
|
|
4
|
+
|
|
5
|
+
RULE { ?x :childOf ?y } WHERE { ?y :fatherOf ?x }
|
|
6
|
+
RULE { ?x :childOf ?y } WHERE { ?y :motherOf ?x }
|
|
7
|
+
|
|
8
|
+
RULE { ?x :descendedFrom ?y } WHERE { ?x :childOf ?y }
|
|
9
|
+
RULE { ?x :descendedFrom ?y } WHERE { ?x :childOf ?z . ?z :childOf ?y }
|
|
10
|
+
|
|
11
|
+
RULE { ?x :ancestorOf ?y } WHERE { ?y :descendedFrom ?x }
|
|
12
|
+
RULE { ?a :ancestorOf ?b } WHERE { ?a :ancestorOf ?c . ?c :ancestorOf ?b }
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
PREFIX : <http://example/#>
|
|
2
|
+
|
|
3
|
+
DATA {
|
|
4
|
+
:alice :name "Alice" .
|
|
5
|
+
:bob :name "Bob" .
|
|
6
|
+
:eve :name "Eve" .
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
# Demonstrates De Morgan push-down:
|
|
10
|
+
# !(A || B) ==> (!A && !B)
|
|
11
|
+
RULE { ?p :passesNameFilter true . }
|
|
12
|
+
WHERE {
|
|
13
|
+
?p :name ?n .
|
|
14
|
+
FILTER ( !( CONTAINS(?n, "o") || STRSTARTS(?n, "E") ) )
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
PREFIX : <http://example/#>
|
|
2
|
+
|
|
3
|
+
DATA {
|
|
4
|
+
:a :score 1 .
|
|
5
|
+
:b :score 2 .
|
|
6
|
+
:c :score 3 .
|
|
7
|
+
:d :score 4 .
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
# Demonstrates IN/NOT IN (mapped to list:in + log:notIncludes)
|
|
11
|
+
RULE { ?x :isSpecial true . }
|
|
12
|
+
WHERE {
|
|
13
|
+
?x :score ?s .
|
|
14
|
+
FILTER ( (?s IN (1, 3, 5)) && (?s NOT IN (2, 4)) )
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
PREFIX : <http://example/#>
|
|
2
|
+
|
|
3
|
+
DATA {
|
|
4
|
+
:bob :age 20 ; :name "Bob" .
|
|
5
|
+
:alice :age 15 ; :name "Alice" .
|
|
6
|
+
:bea :age 22 ; :name "Bea" .
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
# Demonstrates nested OR distribution:
|
|
10
|
+
# ?age >= 18 && ( STRSTARTS(?name,"A") || REGEX(?name,"^B") )
|
|
11
|
+
RULE { ?p :adultNamedByRule true . }
|
|
12
|
+
WHERE {
|
|
13
|
+
?p :age ?age ; :name ?name .
|
|
14
|
+
FILTER ( ?age >= 18 && ( STRSTARTS(?name, "A") || REGEX(?name, "^B") ) )
|
|
15
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
PREFIX : <http://example/#>
|
|
2
|
+
|
|
3
|
+
DATA { :x :p 1 ; :q 2 . }
|
|
4
|
+
|
|
5
|
+
RULE { ?x :bothPositive true . }
|
|
6
|
+
WHERE { ?x :p ?v1 FILTER ( ?v1 > 0 ) ?x :q ?v2 FILTER ( ?v2 > 0 ) }
|
|
7
|
+
|
|
8
|
+
RULE { ?x :oneIsZero true . }
|
|
9
|
+
WHERE { ?x :p ?v1 ; :q ?v2 FILTER ( ( ?v1 = 0 ) || ( ?v2 = 0 ) ) }
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# =================================================
|
|
2
|
+
# JSON Pointer example
|
|
3
|
+
# See https://datatracker.ietf.org/doc/html/rfc6901
|
|
4
|
+
# =================================================
|
|
5
|
+
|
|
6
|
+
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
|
7
|
+
@prefix string: <http://www.w3.org/2000/10/swap/string#> .
|
|
8
|
+
@prefix list: <http://www.w3.org/2000/10/swap/list#> .
|
|
9
|
+
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
10
|
+
@prefix ex: <http://example.org/#> .
|
|
11
|
+
|
|
12
|
+
ex:doc ex:json """{
|
|
13
|
+
"users": [
|
|
14
|
+
{ "id": "u1", "email": "ada@example.org", "profile/name": "Ada Lovelace" },
|
|
15
|
+
{ "id": "u2", "email": "bob@evil.invalid", "profile/name": "Bob Mallory" }
|
|
16
|
+
],
|
|
17
|
+
"policy": { "allowedDomains": ["example.org", "example.com"] }
|
|
18
|
+
}"""^^rdf:JSON .
|
|
19
|
+
|
|
20
|
+
# Sanity check: key contains "/" so JSON Pointer must use "~1"
|
|
21
|
+
{
|
|
22
|
+
ex:doc ex:json ?J .
|
|
23
|
+
(?J "/users/0/profile~1name") string:jsonPointer "Ada Lovelace" .
|
|
24
|
+
} => {
|
|
25
|
+
ex:checks ex:firstUserNameOk true .
|
|
26
|
+
} .
|
|
27
|
+
|
|
28
|
+
# Allowed users: email domain is in policy.allowedDomains
|
|
29
|
+
{
|
|
30
|
+
ex:doc ex:json ?J .
|
|
31
|
+
(?J "/policy/allowedDomains") string:jsonPointer ?Allowed .
|
|
32
|
+
(?J "/users") string:jsonPointer ?Users .
|
|
33
|
+
|
|
34
|
+
?Users list:iterate (?Idx ?UserJson) .
|
|
35
|
+
(?UserJson "/id") string:jsonPointer ?Id .
|
|
36
|
+
(?UserJson "/email") string:jsonPointer ?Email .
|
|
37
|
+
(?UserJson "/profile~1name") string:jsonPointer ?Name .
|
|
38
|
+
|
|
39
|
+
(?Email "@([^@]+)$") string:scrape ?EmailDomain .
|
|
40
|
+
?Allowed list:member ?EmailDomain .
|
|
41
|
+
|
|
42
|
+
("urn:example:user:%s" ?Id) string:format ?UriStr .
|
|
43
|
+
?User log:uri ?UriStr .
|
|
44
|
+
} => {
|
|
45
|
+
?User a ex:AllowedUser ;
|
|
46
|
+
ex:name ?Name ;
|
|
47
|
+
ex:email ?Email ;
|
|
48
|
+
ex:emailDomain ?EmailDomain ;
|
|
49
|
+
ex:userIndex ?Idx .
|
|
50
|
+
} .
|
|
51
|
+
|
|
52
|
+
# Blocked users: email domain is NOT in the allowlist
|
|
53
|
+
{
|
|
54
|
+
ex:doc ex:json ?J .
|
|
55
|
+
(?J "/policy/allowedDomains") string:jsonPointer ?Allowed .
|
|
56
|
+
(?J "/users") string:jsonPointer ?Users .
|
|
57
|
+
|
|
58
|
+
?Users list:iterate (?Idx ?UserJson) .
|
|
59
|
+
(?UserJson "/id") string:jsonPointer ?Id .
|
|
60
|
+
(?UserJson "/email") string:jsonPointer ?Email .
|
|
61
|
+
(?UserJson "/profile~1name") string:jsonPointer ?Name .
|
|
62
|
+
|
|
63
|
+
(?Email "@([^@]+)$") string:scrape ?EmailDomain .
|
|
64
|
+
?Allowed list:notMember ?EmailDomain .
|
|
65
|
+
|
|
66
|
+
("urn:example:user:%s" ?Id) string:format ?UriStr .
|
|
67
|
+
?User log:uri ?UriStr .
|
|
68
|
+
} => {
|
|
69
|
+
?User a ex:BlockedUser ;
|
|
70
|
+
ex:name ?Name ;
|
|
71
|
+
ex:email ?Email ;
|
|
72
|
+
ex:emailDomain ?EmailDomain ;
|
|
73
|
+
ex:userIndex ?Idx .
|
|
74
|
+
} .
|
|
75
|
+
|