$input */ private function shouldParse($createParser, $input = ''): void { $expected = \array_slice(\func_get_args(), 1); // Shift parameters as necessary if (\is_callable($createParser)) { array_shift($expected); } else { $input = $createParser; $createParser = function () { return new TriGParser(); }; } $results = []; $items = array_map(function ($item) { return ['subject' => $item[0], 'predicate' => $item[1], 'object' => $item[2], 'graph' => isset($item[3]) ? $item[3] : '']; }, $expected); $parser = $createParser(); $parser->_resetBlankNodeIds(); $parser->parse($input, function ($error, $triple = null) use (&$results, &$items) { //expect($error).not.to.exist; if ($triple) { $results[] = $triple; } elseif ($error) { throw $error; } else { $this->assertEquals(self::toSortedJSON($items), self::toSortedJSON($results)); } }); } /** * @param string $input * @param string|null $expectedError */ public function shouldNotParse($createParser, $input, $expectedError = null): void { $expected = \array_slice(\func_get_args(), 1); // Shift parameters as necessary if (!\is_callable($createParser)) { $expectedError = $input; $input = $createParser; $createParser = function () { return new TriGParser(); }; } $parser = $createParser(); $parser->_resetBlankNodeIds(); //hackish way so we only act upon first error $errorReceived = false; $parser->parse($input, function ($error, $triple = null) use ($expectedError, &$errorReceived) { //expect($error).not.to.exist; if (isset($error) && !$errorReceived) { $errorReceived = true; $this->assertEquals($expectedError, $error->getMessage()); } elseif (!isset($triple) && !$errorReceived) { $errorReceived = true; $this->fail("Expected this error to be thrown (but it wasn't): ".$expectedError); } }); if (false === $errorReceived) { $this->fail("Expected this error to be thrown (but it wasn't): ".$expectedError); } } /** * @param string $baseIri * @param string $relativeIri * @param string $expected */ public function itShouldResolve($baseIri, $relativeIri, $expected): void { $done = false; try { $doc = ' <'.$relativeIri.'>.'; $parser = new TriGParser(['documentIRI' => $baseIri]); $parser->parse($doc, function ($error, $triple) use (&$done, &$expected) { if (!$done && $triple) { $this->assertEquals($expected, $triple['object']); } if (isset($error)) { $this->fail($error); } $done = true; }); } catch (\Exception $error) { $this->fail("Resolving <$relativeIri> against <$baseIri>.\nError message: ".$error->getMessage()); } } /** * @param array $items */ private static function toSortedJSON(array $items): string { $triples = array_map('json_encode', $items); sort($triples); return "[\n ".implode("\n ", $triples)."\n]"; } public function testZeroOrMoreTriples(): void { // should parse the empty string $this->shouldParse('' /* no triples */); // should parse a whitespace string $this->shouldParse(" \t \n " /* no triples */); // should parse a single triple $this->shouldParse(' .', ['a', 'b', 'c']); // should parse three triples $this->shouldParse(" .\n .\n .", ['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i']); // should parse a triple with a literal $this->shouldParse(' "string".', ['a', 'b', '"string"']); // should parse a triple with a numeric literal $this->shouldParse(' 3.0.', ['a', 'b', '"3.0"^^http://www.w3.org/2001/XMLSchema#decimal']); // should parse a triple with an integer literal $this->shouldParse(' 3.', ['a', 'b', '"3"^^http://www.w3.org/2001/XMLSchema#integer']); // should parse a triple with a floating point literal $this->shouldParse(' 1.3e2.', ['a', 'b', '"1.3e2"^^http://www.w3.org/2001/XMLSchema#double']); // should parse a triple with a boolean literal $this->shouldParse(' true.', ['a', 'b', '"true"^^http://www.w3.org/2001/XMLSchema#boolean']); // should parse a triple with a literal and a language code $this->shouldParse(' "string"@en.', ['a', 'b', '"string"@en']); // should normalize language codes to lowercase $this->shouldParse(' "string"@EN.', ['a', 'b', '"string"@en']); // should parse a triple with a literal and an IRI type $this->shouldParse(' "string"^^.', ['a', 'b', '"string"^^type']); // should parse a triple with a literal and a prefixed name type $this->shouldParse('@prefix x: . "string"^^x:z.', ['a', 'b', '"string"^^y#z']); // should differentiate between IRI and prefixed name types $this->shouldParse('@prefix : . :a :b "x"^^. :a :b "x"^^:urn:foo.', ['noturn:a', 'noturn:b', '"x"^^urn:foo'], ['noturn:a', 'noturn:b', '"x"^^noturn:urn:foo']); // should not parse a triple with a literal and a prefixed name type with an inexistent prefix $this->shouldNotParse(' "string"^^x:z.', 'Undefined prefix "x:" on line 1.'); // should parse a triple with the "a" shorthand predicate $this->shouldParse(' a .', ['a', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 't']); // should parse triples with prefixes $this->shouldParse("@prefix : <#>.\n". "@prefix a: .\n". ':x a:a a:b.', ['#x', 'a#a', 'a#b']); // should parse triples with the prefix "prefix" $this->shouldParse('@prefix prefix: .'. 'prefix:a prefix:b prefix:c.', ['http://prefix.cc/a', 'http://prefix.cc/b', 'http://prefix.cc/c']); // should parse triples with the prefix "base" $this->shouldParse('PREFIX base: '. 'base:a base:b base:c.', ['http://prefix.cc/a', 'http://prefix.cc/b', 'http://prefix.cc/c']); // should parse triples with the prefix "graph" $this->shouldParse('PREFIX graph: '. 'graph:a graph:b graph:c.', ['http://prefix.cc/a', 'http://prefix.cc/b', 'http://prefix.cc/c']); // should not parse @PREFIX $this->shouldNotParse('@PREFIX : <#>.', 'Expected entity but got @PREFIX on line 1.'); // should parse triples with prefixes and different punctuation $this->shouldParse("@prefix : <#>.\n". "@prefix a: .\n". ':x a:a a:b;a:c a:d,a:e.', ['#x', 'a#a', 'a#b'], ['#x', 'a#c', 'a#d'], ['#x', 'a#c', 'a#e']); // should not parse undefined empty prefix in subject $this->shouldNotParse(':a ', 'Undefined prefix ":" on line 1.'); // should not parse undefined prefix in subject $this->shouldNotParse('a:a ', 'Undefined prefix "a:" on line 1.'); // should not parse undefined prefix in predicate $this->shouldNotParse(' b:c .', 'Undefined prefix "b:" on line 1.'); // should not parse undefined prefix in object $this->shouldNotParse(' c:d .', 'Undefined prefix "c:" on line 1.'); // should not parse undefined prefix in datatype $this->shouldNotParse(' "c"^^d:e .', 'Undefined prefix "d:" on line 1.'); // should parse triples with SPARQL prefixes $this->shouldParse("PREFIX : <#>\n". 'PrEfIX a: '. ':x a:a a:b.', ['#x', 'a#a', 'a#b']); // should not parse prefix declarations without prefix $this->shouldNotParse('@prefix ', 'Expected prefix to follow @prefix on line 1.'); // should not parse prefix declarations without IRI $this->shouldNotParse('@prefix : .', 'Expected IRI to follow prefix ":" on line 1.'); // should not parse prefix declarations without a dot $this->shouldNotParse('@prefix : ;', 'Expected declaration to end with a dot on line 1.'); // should parse statements with shared subjects $this->shouldParse(" ;\n .", ['a', 'b', 'c'], ['a', 'd', 'e']); // should parse statements with shared subjects and trailing semicolon $this->shouldParse(" ;\n ;\n.", ['a', 'b', 'c'], ['a', 'd', 'e']); // should parse statements with shared subjects and multiple semicolons $this->shouldParse(" ;;\n .", ['a', 'b', 'c'], ['a', 'd', 'e']); // should parse statements with shared subjects and predicates $this->shouldParse(' , .', ['a', 'b', 'c'], ['a', 'b', 'd']); } public function testBlankNodes(): void { // should throw an error on empty list item with lacking document base IRI $this->shouldNotParse("(<>) <> (<>) <>.", "list item on line 1 can not be parsed without knowing the the document base IRI.\n". "Please set the document base IRI using the documentIRI parser configuration option.\n". "See https://github.com/pietercolpaert/hardf/#empty-document-base-IRI ."); // but should manage if the parser has documentIRI set $this->shouldParse(function () { return new TriGParser(['documentIRI' => 'http://base/']); }, "(<>) <> (<>) <>.", ['_:b0', 'http://base/', '_:b1', 'http://base/'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'http://base/'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'http://base/'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil']); // should parse statements with named blank nodes $this->shouldParse('_:a _:c.', ['_:b0_a', 'b', '_:b0_c']); // should not parse statements with blank predicates $this->shouldNotParse(' _:b .', 'Disallowed blank node as predicate on line 1.'); // should parse statements with empty blank nodes $this->shouldParse('[] [].', ['_:b0', 'b', '_:b1']); // should parse statements with unnamed blank nodes in the subject $this->shouldParse('[ ] .', ['_:b0', 'c', 'd'], ['_:b0', 'a', 'b']); // should parse statements with unnamed blank nodes in the object $this->shouldParse(' [ ].', ['a', 'b', '_:b0'], ['_:b0', 'c', 'd']); // should parse statements with unnamed blank nodes with a string object $this->shouldParse(' [ "x"].', ['a', 'b', '_:b0'], ['_:b0', 'c', '"x"']); // should not parse a blank node with missing subject $this->shouldNotParse(' [].', 'Expected entity but got ] on line 1.'); // should not parse a blank node with only a semicolon $this->shouldNotParse(' [;].', 'Unexpected ] on line 1.'); // should parse a blank node with a trailing semicolon $this->shouldParse(' [ ; ].', ['a', 'b', '_:b0'], ['_:b0', 'u', 'v']); // should parse a blank node with multiple trailing semicolons $this->shouldParse(' [ ;;; ].', ['a', 'b', '_:b0'], ['_:b0', 'u', 'v']); // should parse a multi-predicate blank node $this->shouldParse(' [ ; ].', ['a', 'b', '_:b0'], ['_:b0', 'u', 'v'], ['_:b0', 'w', 'z']); // should parse a multi-predicate blank node with multiple semicolons $this->shouldParse(' [ ;;; ].', ['a', 'b', '_:b0'], ['_:b0', 'u', 'v'], ['_:b0', 'w', 'z']); // should parse a multi-object blank node $this->shouldParse(' [ , ].', ['a', 'b', '_:b0'], ['_:b0', 'u', 'v'], ['_:b0', 'u', 'z']); // should parse a multi-statement blank node ending with a literal $this->shouldParse(' [ ; "z" ].', ['a', 'b', '_:b0'], ['_:b0', 'u', 'v'], ['_:b0', 'w', '"z"']); // should parse a multi-statement blank node ending with a typed literal $this->shouldParse(' [ ; "z"^^ ].', ['a', 'b', '_:b0'], ['_:b0', 'u', 'v'], ['_:b0', 'w', '"z"^^t']); // should parse a multi-statement blank node ending with a string with language $this->shouldParse(' [ ; "z"^^ ].', ['a', 'b', '_:b0'], ['_:b0', 'u', 'v'], ['_:b0', 'w', '"z"^^t']); // should parse a multi-statement blank node with trailing semicolon $this->shouldParse(' [ ; ; ].', ['a', 'b', '_:b0'], ['_:b0', 'u', 'v'], ['_:b0', 'w', 'z']); // should parse statements with nested blank nodes in the subject $this->shouldParse('[ [ ]] .', ['_:b0', 'c', 'd'], ['_:b0', 'a', '_:b1'], ['_:b1', 'x', 'y']); // should parse statements with nested blank nodes in the object $this->shouldParse(' [ [ ]].', ['a', 'b', '_:b0'], ['_:b0', 'c', '_:b1'], ['_:b1', 'd', 'e']); // should reuse identifiers of blank nodes within and outside of graphs $this->shouldParse('_:a _:c. { _:a _:c }', ['_:b0_a', 'b', '_:b0_c'], ['_:b0_a', 'b', '_:b0_c', 'g']); // should not parse an invalid blank node $this->shouldNotParse('[ .', 'Expected punctuation to follow "b" on line 1.'); // should parse a statements with only an anonymous node $this->shouldParse('[

].', ['_:b0', 'p', 'o']); // should not parse a statement with only a blank anonymous node $this->shouldNotParse('[].', 'Unexpected . on line 1.'); // should not parse an anonymous node with only an anonymous node inside $this->shouldNotParse('[[

]].', 'Expected entity but got [ on line 1.'); // should parse statements with an empty list in the subject $this->shouldParse('() .', ['http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', 'a', 'b']); // should parse statements with an empty list in the object $this->shouldParse(' ().', ['a', 'b', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil']); // should parse statements with a single-element list in the subject $this->shouldParse('() .', ['_:b0', 'a', 'b'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'x'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil']); // should parse statements with a single-element list in the object $this->shouldParse(' ().', ['a', 'b', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'x'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil']); // should parse a list with a literal $this->shouldParse(' ("x").', ['a', 'b', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '"x"'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil']); // should parse a list with a typed literal $this->shouldParse(' ("x"^^).', ['a', 'b', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '"x"^^y'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil']); // should parse a list with a language-tagged literal $this->shouldParse(' ("x"@en-GB).', ['a', 'b', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '"x"@en-gb'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil']); // should parse statements with a multi-element list in the subject $this->shouldParse('( ) .', ['_:b0', 'a', 'b'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'x'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b1'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'y'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil']); // should parse statements with a multi-element list in the object $this->shouldParse(' ( ).', ['a', 'b', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'x'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b1'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'y'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil']); // should parse statements with a multi-element literal list in the object $this->shouldParse(' ("x" "y"@en-GB "z"^^).', ['a', 'b', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '"x"'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b1'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '"y"@en-gb'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b2'], ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '"z"^^t'], ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil']); // should parse statements with prefixed names in lists $this->shouldParse('@prefix a: . (a:x a:y).', ['a', 'b', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'a#x'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b1'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'a#y'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil']); // should not parse statements with undefined prefixes in lists $this->shouldNotParse(' (a:x a:y).', 'Undefined prefix "a:" on line 1.'); // should parse statements with blank nodes in lists $this->shouldParse(' (_:x _:y).', ['a', 'b', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b0_x'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b1'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b0_y'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil']); // should parse statements with a nested empty list $this->shouldParse(' ( ()).', ['a', 'b', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'x'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b1'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil']); // should parse statements with non-empty nested lists $this->shouldParse(' ( ()).', ['a', 'b', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'x'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b1'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b2'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'y'], ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil']); // should parse statements with a list containing a blank node $this->shouldParse('([]) .', ['_:b0', 'a', 'b'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b1'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil']); // should parse statements with a list containing multiple blank nodes $this->shouldParse('([] [ ]) .', ['_:b0', 'a', 'b'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b1'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b2'], ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b3'], ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], ['_:b3', 'x', 'y']); // should parse statements with a blank node containing a list $this->shouldParse('[ ()] .', ['_:b0', 'c', 'd'], ['_:b0', 'a', '_:b1'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'b'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil']); // should not parse an invalid list $this->shouldNotParse(' (]).', 'Expected entity but got ] on line 1.'); // should resolve IRIs against @base $this->shouldParse("@base .\n". " .\n". "@base .\n". ' .', ['http://ex.org/a', 'http://ex.org/b', 'http://ex.org/c'], ['http://ex.org/d/e', 'http://ex.org/d/f', 'http://ex.org/d/g']); // should not resolve IRIs against @BASE $this->shouldNotParse('@BASE .', 'Expected entity but got @BASE on line 1.'); // should resolve IRIs against SPARQL base $this->shouldParse("BASE \n". ' . '. 'BASE '. ' .', ['http://ex.org/a', 'http://ex.org/b', 'http://ex.org/c'], ['http://ex.org/d/e', 'http://ex.org/d/f', 'http://ex.org/d/g']); // should resolve IRIs against a @base with query string $this->shouldParse("@base .\n". "<> .\n". "@base .\n". '<> .', ['http://ex.org/?foo', 'http://ex.org/b', 'http://ex.org/c'], ['http://ex.org/d/?bar', 'http://ex.org/d/f', 'http://ex.org/d/g']); // should resolve IRIs with query string against @base $this->shouldParse("@base .\n". " .\n". "@base .\n". ' .'. "@base .\n". '<> .', ['http://ex.org/?', 'http://ex.org/?a', 'http://ex.org/?a=b'], ['http://ex.org/d?', 'http://ex.org/d?a', 'http://ex.org/d?a=b'], ['http://ex.org/d?e', 'http://ex.org/d?a', 'http://ex.org/d?a=b']); // should not resolve IRIs with colons $this->shouldParse("@base .\n". " .\n". " .\n". ' .', ['http://ex.org/a', 'http://ex.org/b', 'http://ex.org/c'], ['A:', 'b:', 'c:'], ['a:a', 'b:B', 'C-D:c']); // should resolve datatype IRIs against @base $this->shouldParse("@base .\n". " \"c\"^^.\n". "@base .\n". ' "g"^^.', ['http://ex.org/a', 'http://ex.org/b', '"c"^^http://ex.org/d'], ['http://ex.org/d/e', 'http://ex.org/d/f', '"g"^^http://ex.org/d/h']); // should resolve IRIs against a base with a fragment $this->shouldParse("@base .\n". " <#c>.\n", ['http://ex.org/a', 'http://ex.org/b', 'http://ex.org/foo#c']); // should resolve IRIs with an empty fragment $this->shouldParse("@base .\n". "<#> <#c>.\n", ['http://ex.org/foo#', 'http://ex.org/b#', 'http://ex.org/foo#c']); // should not resolve prefixed names $this->shouldParse('PREFIX ex: '."\n". 'ex:a ex:b ex:c .', ['http://ex.org/a/bb/ccc/../a', 'http://ex.org/a/bb/ccc/../b', 'http://ex.org/a/bb/ccc/../c']); // should parse an empty default graph $this->shouldParse('{}'); // should parse a one-triple default graph ending without a dot $this->shouldParse('{ }', ['a', 'b', 'c']); // should parse a one-triple default graph ending with a dot $this->shouldParse('{ .}', ['a', 'b', 'c']); // should parse a three-triple default graph ending without a dot $this->shouldParse('{ ; ,}', ['a', 'b', 'c'], ['a', 'd', 'e'], ['a', 'd', 'f']); // should parse a three-triple default graph ending with a dot $this->shouldParse('{ ; ,.}', ['a', 'b', 'c'], ['a', 'd', 'e'], ['a', 'd', 'f']); // should parse a three-triple default graph ending with a semicolon $this->shouldParse('{ ; ,;}', ['a', 'b', 'c'], ['a', 'd', 'e'], ['a', 'd', 'f']); // should parse an empty named graph with an IRI $this->shouldParse('{}'); // should parse a one-triple named graph with an IRI ending without a dot $this->shouldParse(' { }', ['a', 'b', 'c', 'g']); // should parse a one-triple named graph with an IRI ending with a dot $this->shouldParse('{ .}', ['a', 'b', 'c', 'g']); // should parse a three-triple named graph with an IRI ending without a dot $this->shouldParse(' { ; ,}', ['a', 'b', 'c', 'g'], ['a', 'd', 'e', 'g'], ['a', 'd', 'f', 'g']); // should parse a three-triple named graph with an IRI ending with a dot $this->shouldParse('{ ; ,.}', ['a', 'b', 'c', 'g'], ['a', 'd', 'e', 'g'], ['a', 'd', 'f', 'g']); // should parse an empty named graph with a prefixed name $this->shouldParse("@prefix g: .\ng:h {}"); // should parse a one-triple named graph with a prefixed name ending without a dot $this->shouldParse("@prefix g: .\ng:h { }", ['a', 'b', 'c', 'g#h']); // should parse a one-triple named graph with a prefixed name ending with a dot $this->shouldParse("@prefix g: .\ng:h{ .}", ['a', 'b', 'c', 'g#h']); // should parse a three-triple named graph with a prefixed name ending without a dot $this->shouldParse('@prefix g: .'."\n".'g:h { ; ,}', ['a', 'b', 'c', 'g#h'], ['a', 'd', 'e', 'g#h'], ['a', 'd', 'f', 'g#h']); // should parse a three-triple named graph with a prefixed name ending with a dot $this->shouldParse('@prefix g: .'."\n".'g:h{ ; ,.}', ['a', 'b', 'c', 'g#h'], ['a', 'd', 'e', 'g#h'], ['a', 'd', 'f', 'g#h']); // should parse an empty anonymous graph $this->shouldParse('[] {}'); // should parse a one-triple anonymous graph ending without a dot $this->shouldParse('[] { }', ['a', 'b', 'c', '_:b0']); // should parse a one-triple anonymous graph ending with a dot $this->shouldParse('[]{ .}', ['a', 'b', 'c', '_:b0']); // should parse a three-triple anonymous graph ending without a dot $this->shouldParse('[] { ; ,}', ['a', 'b', 'c', '_:b0'], ['a', 'd', 'e', '_:b0'], ['a', 'd', 'f', '_:b0']); // should parse a three-triple anonymous graph ending with a dot $this->shouldParse('[]{ ; ,.}', ['a', 'b', 'c', '_:b0'], ['a', 'd', 'e', '_:b0'], ['a', 'd', 'f', '_:b0']); // should parse an empty named graph with an IRI and the GRAPH keyword $this->shouldParse('GRAPH {}'); // should parse an empty named graph with a prefixed name and the GRAPH keyword $this->shouldParse('@prefix g: .'."\n".'GRAPH g:h {}'); // should parse an empty anonymous graph and the GRAPH keyword $this->shouldParse('GRAPH [] {}'); // should parse a one-triple named graph with an IRI and the GRAPH keyword $this->shouldParse('GRAPH { }', ['a', 'b', 'c', 'g']); // should parse a one-triple named graph with a prefixed name and the GRAPH keyword $this->shouldParse('@prefix g: .'."\n".'GRAPH g:h { }', ['a', 'b', 'c', 'g#h']); // should parse a one-triple anonymous graph and the GRAPH keyword $this->shouldParse('GRAPH [] { }', ['a', 'b', 'c', '_:b0']); } public function testLiterals(): void { // should parse triple quotes $this->shouldParse(' """ abc """.', ['a', 'b', '" abc "']); // should parse triple quotes with a newline $this->shouldParse(" \"\"\" abc\nabc \"\"\".", ['a', 'b', '" abc'."\n".'abc "']); } public function testUnicode(): void { // should parse a graph with 8-bit unicode escape sequences $this->shouldParse('<\\U0001d400> {'."\n".'<\\U0001d400> <\\U0001d400> "\\U0001d400"^^<\\U0001d400>'."\n".'}'."\n", ['๐€', '๐€', '"๐€"^^๐€', '๐€']); $this->shouldParse('@prefix c: . @prefix c: . c:test .', ['http://example.org/test', 'b', 'http://example.org/ใƒ†ใ‚นใƒˆ', '']); // should parse unicode after prefix $this->shouldParse('@prefix c: . c:test c:ใƒ†ใ‚นใƒˆ .', ['http://example.org/test', 'b', 'http://example.org/ใƒ†ใ‚นใƒˆ', '']); // should parse unicode in literal $this->shouldParse('@prefix c: . c:test "c:ใƒ†ใ‚นใƒˆ" .', ['http://example.org/test', 'b', '"c:ใƒ†ใ‚นใƒˆ"', '']); // should parse unicode in prefixname $this->shouldParse('@prefix c: . c:ใ‚นใƒˆ .', ['http://example.org/testprefixname', 'b', 'http://example.org/ใƒ†ใ‚นใƒˆ', '']); } public function testParseErrors(): void { // should not parse a single closing brace $this->shouldNotParse('}', 'Unexpected graph closing on line 1.'); // should not parse a single opening brace $this->shouldNotParse('{', 'Expected entity but got eof on line 1.'); // should not parse a superfluous closing brace $this->shouldNotParse('{}}', 'Unexpected graph closing on line 1.'); // should not parse a graph with only a dot $this->shouldNotParse('{.}', 'Expected entity but got . on line 1.'); // should not parse a graph with only a semicolon $this->shouldNotParse('{;}', 'Expected entity but got ; on line 1.'); // should not parse an unclosed graph $this->shouldNotParse('{ .', 'Unclosed graph on line 1.'); // should not parse a named graph with a list node as label $this->shouldNotParse('() {}', 'Expected entity but got { on line 1.'); // should not parse a named graph with a non-empty blank node as label $this->shouldNotParse('[ ] {}', 'Expected entity but got { on line 1.'); // should not parse a named graph with the GRAPH keyword and a non-empty blank node as label $this->shouldNotParse('GRAPH [ ] {}', 'Invalid graph label on line 1.'); // should not parse a triple after the GRAPH keyword $this->shouldNotParse('GRAPH .', 'Expected graph but got IRI on line 1.'); // should not parse repeated GRAPH keywords $this->shouldNotParse('GRAPH GRAPH {}', 'Invalid graph label on line 1.'); // should parse a quad with 4 IRIs $this->shouldParse(' .', ['a', 'b', 'c', 'g']); // should parse a quad with 4 prefixed names $this->shouldParse('@prefix p: .'."\n".'p:a p:b p:c p:g.', ['p#a', 'p#b', 'p#c', 'p#g']); // should not parse a quad with an undefined prefix $this->shouldNotParse(' p:g.', 'Undefined prefix "p:" on line 1.'); // should parse a quad with 3 IRIs and a literal $this->shouldParse(' "c"^^ .', ['a', 'b', '"c"^^d', 'g']); // should parse a quad with 2 blank nodes and a literal $this->shouldParse('_:a "c"^^ _:g.', ['_:b0_a', 'b', '"c"^^d', '_:b0_g']); // should not parse a quad in a graph $this->shouldNotParse('{ .}', 'Expected punctuation to follow "c" on line 1.'); // should not parse a quad with different punctuation $this->shouldNotParse(' ;', 'Expected dot to follow quad on line 1.'); // should not parse base declarations without IRI $this->shouldNotParse('@base a: ', 'Expected IRI to follow base declaration on line 1.'); // should not parse improperly nested parentheses and brackets $this->shouldNotParse(' [ (]).', 'Expected entity but got ] on line 1.'); // should not parse improperly nested square brackets $this->shouldNotParse(' [ ]].', 'Expected entity but got ] on line 1.'); // should error when an object is not there $this->shouldNotParse(' .', 'Expected entity but got . on line 1.'); // should error when a dot is not there $this->shouldNotParse(' ', 'Expected entity but got eof on line 1.'); // should error with an abbreviation in the subject $this->shouldNotParse('a .', 'Expected entity but got abbreviation on line 1.'); // should error with an abbreviation in the object $this->shouldNotParse(' a .', 'Expected entity but got abbreviation on line 1.'); // should error if punctuation follows a subject $this->shouldNotParse(' .', 'Unexpected . on line 1.'); // should error if an unexpected token follows a subject $this->shouldNotParse(' [', 'Expected entity but got [ on line 1.'); } public function testInterface(): void { $prefixes = []; $tripleCallback = function ($error, $triple) use (&$prefixes) { //when end of stream if (!isset($triple)) { $this->assertEquals(2, \count(array_keys($prefixes))); } }; $prefixCallback = function ($prefix, $iri) use (&$prefixes) { //$this->assertExists($prefix); //$this->assertExists($iri); $prefixes[$prefix] = $iri; }; // should return prefixes through a callback function (new TriGParser())->parse('@prefix a: . a:a a:b a:c. @prefix b: .', $tripleCallback, $prefixCallback); // should return prefixes through a callback without triple callback function (done) { $prefixes = []; $prefixCallback = function ($prefix, $iri) use (&$prefixes) { $prefixes[$prefix] = $iri; }; (new TriGParser())->parse('@prefix a: . a:a a:b a:c. @prefix b: .', null, $prefixCallback); $this->assertEquals(2, \count(array_keys($prefixes))); // should return prefixes at the last triple callback function (done) { $tripleCallback = function ($error, $triple) use (&$prefixes) { if (!isset($triple)) { $this->assertEquals(2, \count(array_keys($prefixes))); } }; (new TriGParser())->parse('@prefix a: . a:a a:b a:c. @prefix b: .', $tripleCallback); // should parse a string synchronously if no callback is given function () { $triples = (new TriGParser())->parse('@prefix a: . a:a a:b a:c.'); $this->assertEquals([['subject' => 'urn:a:a', 'predicate' => 'urn:a:b', 'object' => 'urn:a:c', 'graph' => '']], $triples); } public function testParsingChunks(): void { $count = 0; $parser = new TriGParser([], function ($error, $triple) use (&$count) { if (isset($triple)) { $this->assertEquals($triple, ['subject' => 'http://ex.org/a', 'predicate' => 'http://ex.org/b', 'object' => 'http://ex.org/c', 'graph' => '']); ++$count; } elseif (isset($error)) { throw $error; } }); $parser->parseChunk('@prefix a: . a:a a:b a:c.'."\n"); $parser->parseChunk('@prefix a: . a:a a:b a:c.'."\n"); $parser->parseChunk('@prefix a: . a:a a:b a:c.'."\n"); $this->assertEquals(3, $count); } public function testParsingWithLiteralNewline(): void { // With a newline $count = 0; $parser = new TriGParser([], function ($error, $triple) use (&$count) { if (isset($triple)) { $this->assertEquals($triple, ['subject' => 'http://ex.org/a', 'predicate' => 'http://ex.org/b', 'object' => "\"\n\"", 'graph' => '']); ++$count; } elseif (!isset($triple) && isset($error)) { throw $error; } }); $parser->parseChunk('@prefix a: . a:a a:b """'."\n"); $parser->parseChunk('""".'); $parser->end(); $this->assertEquals(1, $count); } public function testException(): void { // should throw on syntax errors if no callback is given function () { try { (new TriGParser())->parse(' bar '); } catch (\Exception $e) { $this->assertEquals('Unexpected "bar" on line 1.', $e->getMessage()); } // should throw on grammar errors if no callback is given function () { try { (new TriGParser())->parse(' '); } catch (\Exception $e) { $this->assertEquals('Expected punctuation to follow "c" on line 1.', $e->getMessage()); } } public function testParserWithIRI(): void { $parser = function () { return new TriGParser(['documentIRI' => 'http://ex.org/x/yy/zzz/f.ttl']); }; // should resolve IRIs against the document IRI $this->shouldParse($parser, '@prefix : <#>.'."\n". ' .'."\n". ':d :e :f :g.', ['http://ex.org/x/yy/zzz/a', 'http://ex.org/x/yy/zzz/b', 'http://ex.org/x/yy/zzz/c', 'http://ex.org/x/yy/zzz/g'], ['http://ex.org/x/yy/zzz/f.ttl#d', 'http://ex.org/x/yy/zzz/f.ttl#e', 'http://ex.org/x/yy/zzz/f.ttl#f', 'http://ex.org/x/yy/zzz/f.ttl#g']); // should resolve IRIs with a trailing slash against the document IRI $this->shouldParse($parser, ' .'."\n", ['http://ex.org/a', 'http://ex.org/a/b', 'http://ex.org/a/b/c']); // should resolve IRIs starting with ./ against the document IRI $this->shouldParse($parser, '<./a> <./a/b> <./a/b/c>.'."\n", ['http://ex.org/x/yy/zzz/a', 'http://ex.org/x/yy/zzz/a/b', 'http://ex.org/x/yy/zzz/a/b/c']); // should resolve IRIs starting with multiple ./ sequences against the document IRI $this->shouldParse($parser, '<./././a> <./././././a/b> <././././././a/b/c>.'."\n", ['http://ex.org/x/yy/zzz/a', 'http://ex.org/x/yy/zzz/a/b', 'http://ex.org/x/yy/zzz/a/b/c']); // should resolve IRIs starting with ../ against the document IRI $this->shouldParse($parser, '<../a> <../a/b> <../a/b/c>.'."\n", ['http://ex.org/x/yy/a', 'http://ex.org/x/yy/a/b', 'http://ex.org/x/yy/a/b/c']); // should resolve IRIs starting multiple ../ sequences against the document IRI $this->shouldParse($parser, '<../../a> <../../../a/b> <../../../../../../../../a/b/c>.'."\n", ['http://ex.org/x/a', 'http://ex.org/a/b', 'http://ex.org/a/b/c']); // should resolve IRIs starting with mixes of ./ and ../ sequences against the document IRI $this->shouldParse($parser, '<.././a> <./.././a/b> <./.././.././a/b/c>.'."\n", ['http://ex.org/x/yy/a', 'http://ex.org/x/yy/a/b', 'http://ex.org/x/a/b/c']); // should resolve IRIs starting with .x, ..x, or .../ against the document IRI $this->shouldParse($parser, '<.x/a> <..x/a/b> <.../a/b/c>.'."\n", ['http://ex.org/x/yy/zzz/.x/a', 'http://ex.org/x/yy/zzz/..x/a/b', 'http://ex.org/x/yy/zzz/.../a/b/c']); // should resolve datatype IRIs against the document IRI $this->shouldParse($parser, ' "c"^^.', ['http://ex.org/x/yy/zzz/a', 'http://ex.org/x/yy/zzz/b', '"c"^^http://ex.org/x/yy/zzz/d']); // should resolve IRIs in lists against the document IRI $this->shouldParse($parser, '( )

( ).', ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'http://ex.org/x/yy/zzz/a'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b1'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'http://ex.org/x/yy/zzz/b'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], ['_:b0', 'http://ex.org/x/yy/zzz/p', '_:b2'], ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'http://ex.org/x/yy/zzz/c'], ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b3'], ['_:b3', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'http://ex.org/x/yy/zzz/d'], ['_:b3', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil']); // should respect @base statements $this->shouldParse($parser, ' .'."\n". '@base .'."\n". ' .'."\n". '@base .'."\n". ' .'."\n". '@base .'."\n". ' .', ['http://ex.org/x/yy/zzz/a', 'http://ex.org/x/yy/zzz/b', 'http://ex.org/x/yy/zzz/c'], ['http://ex.org/x/e', 'http://ex.org/x/f', 'http://ex.org/x/g'], ['http://ex.org/x/d/h', 'http://ex.org/x/d/i', 'http://ex.org/x/d/j'], ['http://ex.org/e/k', 'http://ex.org/e/l', 'http://ex.org/e/m']); } public function testParserWithDocumentIRI(): void { $parser = function () { return new TriGParser(['documentIRI' => 'http://ex.org/x/yy/zzz/f.ttl']); }; // should resolve IRIs against the document IRI $this->shouldParse($parser, '@prefix : <#>.'."\n". ' .'."\n". ':d :e :f :g.', ['http://ex.org/x/yy/zzz/a', 'http://ex.org/x/yy/zzz/b', 'http://ex.org/x/yy/zzz/c', 'http://ex.org/x/yy/zzz/g'], ['http://ex.org/x/yy/zzz/f.ttl#d', 'http://ex.org/x/yy/zzz/f.ttl#e', 'http://ex.org/x/yy/zzz/f.ttl#f', 'http://ex.org/x/yy/zzz/f.ttl#g']); // should resolve IRIs with a trailing slash against the document IRI $this->shouldParse($parser, ' .'."\n", ['http://ex.org/a', 'http://ex.org/a/b', 'http://ex.org/a/b/c']); // should resolve IRIs starting with ./ against the document IRI $this->shouldParse($parser, '<./a> <./a/b> <./a/b/c>.'."\n", ['http://ex.org/x/yy/zzz/a', 'http://ex.org/x/yy/zzz/a/b', 'http://ex.org/x/yy/zzz/a/b/c']); // should resolve IRIs starting with multiple ./ sequences against the document IRI $this->shouldParse($parser, '<./././a> <./././././a/b> <././././././a/b/c>.'."\n", ['http://ex.org/x/yy/zzz/a', 'http://ex.org/x/yy/zzz/a/b', 'http://ex.org/x/yy/zzz/a/b/c']); // should resolve IRIs starting with ../ against the document IRI $this->shouldParse($parser, '<../a> <../a/b> <../a/b/c>.'."\n", ['http://ex.org/x/yy/a', 'http://ex.org/x/yy/a/b', 'http://ex.org/x/yy/a/b/c']); // should resolve IRIs starting multiple ../ sequences against the document IRI $this->shouldParse($parser, '<../../a> <../../../a/b> <../../../../../../../../a/b/c>.'."\n", ['http://ex.org/x/a', 'http://ex.org/a/b', 'http://ex.org/a/b/c']); // should resolve IRIs starting with mixes of ./ and ../ sequences against the document IRI $this->shouldParse($parser, '<.././a> <./.././a/b> <./.././.././a/b/c>.'."\n", ['http://ex.org/x/yy/a', 'http://ex.org/x/yy/a/b', 'http://ex.org/x/a/b/c']); // should resolve IRIs starting with .x, ..x, or .../ against the document IRI $this->shouldParse($parser, '<.x/a> <..x/a/b> <.../a/b/c>.'."\n", ['http://ex.org/x/yy/zzz/.x/a', 'http://ex.org/x/yy/zzz/..x/a/b', 'http://ex.org/x/yy/zzz/.../a/b/c']); // should resolve datatype IRIs against the document IRI $this->shouldParse($parser, ' "c"^^.', ['http://ex.org/x/yy/zzz/a', 'http://ex.org/x/yy/zzz/b', '"c"^^http://ex.org/x/yy/zzz/d']); // should resolve IRIs in lists against the document IRI $this->shouldParse($parser, '( )

( ).', ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'http://ex.org/x/yy/zzz/a'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b1'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'http://ex.org/x/yy/zzz/b'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], ['_:b0', 'http://ex.org/x/yy/zzz/p', '_:b2'], ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'http://ex.org/x/yy/zzz/c'], ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b3'], ['_:b3', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'http://ex.org/x/yy/zzz/d'], ['_:b3', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil']); // should respect @base statements $this->shouldParse($parser, ' .'."\n". '@base .'."\n". ' .'."\n". '@base .'."\n". ' .'."\n". '@base .'."\n". ' .', ['http://ex.org/x/yy/zzz/a', 'http://ex.org/x/yy/zzz/b', 'http://ex.org/x/yy/zzz/c'], ['http://ex.org/x/e', 'http://ex.org/x/f', 'http://ex.org/x/g'], ['http://ex.org/x/d/h', 'http://ex.org/x/d/i', 'http://ex.org/x/d/j'], ['http://ex.org/e/k', 'http://ex.org/e/l', 'http://ex.org/e/m']); } public function testDifferentSettings(): void { $parser = function () { return new TriGParser(['blankNodePrefix' => '_:blank']); }; // should use the given prefix for blank nodes $this->shouldParse($parser, '_:a _:c.'."\n", ['_:blanka', 'b', '_:blankc']); $parser = function () { return new TriGParser(['blankNodePrefix' => '']); }; // should not use a prefix for blank nodes $this->shouldParse($parser, '_:a _:c.'."\n", ['_:a', 'b', '_:c']); $parser = function () { return new TriGParser(['format' => 1]); }; // should parse a single triple $this->shouldParse($parser, ' .', ['a', 'b', 'c']); // should parse a graph $this->shouldParse($parser, '{ }', ['a', 'b', 'c']); } public function testTurtle(): void { $parser = function () { return new TriGParser(['format' => 'Turtle']); }; // should parse a single triple $this->shouldParse($parser, ' .', ['a', 'b', 'c']); // should not parse a default graph $this->shouldNotParse($parser, '{}', 'Unexpected graph on line 1.'); // should not parse a named graph $this->shouldNotParse($parser, ' {}', 'Expected entity but got { on line 1.'); // should not parse a named graph with the GRAPH keyword $this->shouldNotParse($parser, 'GRAPH {}', 'Expected entity but got GRAPH on line 1.'); // should not parse a quad $this->shouldNotParse($parser, ' .', 'Expected punctuation to follow "c" on line 1.'); // should not parse a variable $this->shouldNotParse($parser, '?a ?b ?c.', 'Unexpected "?a" on line 1.'); // should not parse an equality statement $this->shouldNotParse($parser, ' = .', 'Unexpected "=" on line 1.'); // should not parse a right implication statement $this->shouldNotParse($parser, ' => .', 'Unexpected "=>" on line 1.'); // should not parse a left implication statement $this->shouldNotParse($parser, ' <= .', 'Unexpected "<=" on line 1.'); // should not parse a formula as object $this->shouldNotParse($parser, ' {}.', 'Unexpected graph on line 1.'); } public function testTriGFormat(): void { $parser = function () { return new TriGParser(['format' => 'TriG']); }; // should parse a single triple $this->shouldParse($parser, ' .', ['a', 'b', 'c']); // should parse a default graph $this->shouldParse($parser, '{}'); // should parse a named graph $this->shouldParse($parser, ' {}'); // should parse a named graph with the GRAPH keyword $this->shouldParse($parser, 'GRAPH {}'); // should not parse a quad $this->shouldNotParse($parser, ' .', 'Expected punctuation to follow "c" on line 1.'); // should not parse a variable $this->shouldNotParse($parser, '?a ?b ?c.', 'Unexpected "?a" on line 1.'); // should not parse an equality statement $this->shouldNotParse($parser, ' = .', 'Unexpected "=" on line 1.'); // should not parse a right implication statement $this->shouldNotParse($parser, ' => .', 'Unexpected "=>" on line 1.'); // should not parse a left implication statement $this->shouldNotParse($parser, ' <= .', 'Unexpected "<=" on line 1.'); // should not parse a formula as object $this->shouldNotParse($parser, ' {}.', 'Unexpected graph on line 1.'); } public function testNTriplesFormat(): void { $parser = function () { return new TriGParser(['format' => 'N-Triples', 'blankNodePrefix' => '']); }; // should parse a single triple $this->shouldParse($parser, ' "c".', ['http://ex.org/a', 'http://ex.org/b', '"c"']); // should not parse a single quad $this->shouldNotParse($parser, ' "c" .', 'Expected punctuation to follow ""c"" on line 1.'); // should not parse relative IRIs $this->shouldNotParse($parser, ' .', 'Disallowed relative IRI on line 1.'); // should not parse a prefix declaration $this->shouldNotParse($parser, '@prefix : .', 'Unexpected "@prefix" on line 1.'); // should not parse a variable $this->shouldNotParse($parser, '?a ?b ?c.', 'Unexpected "?a" on line 1.'); // should not parse an equality statement $this->shouldNotParse($parser, ' = .', 'Unexpected "=" on line 1.'); // should not parse a right implication statement $this->shouldNotParse($parser, ' => .', 'Unexpected "=>" on line 1.'); // should not parse a left implication statement $this->shouldNotParse($parser, ' <= .', 'Unexpected "<=" on line 1.'); // should not parse a formula as object $this->shouldNotParse($parser, ' {}.', 'Unexpected "{" on line 1.'); // https://github.com/pietercolpaert/hardf/issues/32 $this->shouldParse($parser, ' "1"^^ .', ['http://a.example/s', 'http://a.example/p', '"1"^^http://www.w3.org/2001/XMLSchema#integer']); // https://github.com/pietercolpaert/hardf/issues/34 $this->shouldParse($parser, '_:r1 "baz".', ['_:r1', 'https://foo.bar', '"baz"']); } public function testNQuadsFormat(): void { $parser = function () { return new TriGParser(['format' => 'N-Quads']); }; // should parse a single triple $this->shouldParse($parser, ' .', ['http://ex.org/a', 'http://ex.org/b', 'http://ex.org/c']); // should parse a single quad $this->shouldParse($parser, ' "c" .', ['http://ex.org/a', 'http://ex.org/b', '"c"', 'http://ex.org/g']); // should not parse relative IRIs $this->shouldNotParse($parser, ' .', 'Disallowed relative IRI on line 1.'); // should not parse a prefix declaration $this->shouldNotParse($parser, '@prefix : .', 'Unexpected "@prefix" on line 1.'); // should not parse a variable $this->shouldNotParse($parser, '?a ?b ?c.', 'Unexpected "?a" on line 1.'); // should not parse an equality statement $this->shouldNotParse($parser, ' = .', 'Unexpected "=" on line 1.'); // should not parse a right implication statement $this->shouldNotParse($parser, ' => .', 'Unexpected "=>" on line 1.'); // should not parse a left implication statement $this->shouldNotParse($parser, ' <= .', 'Unexpected "<=" on line 1.'); // should not parse a formula as object $this->shouldNotParse($parser, ' {}.', 'Unexpected "{" on line 1.'); } public function testN3Format(): void { $parser = function () { return new TriGParser(['format' => 'N3']); }; // should parse a single triple $this->shouldParse($parser, ' .', ['a', 'b', 'c']); // should not parse a default graph $this->shouldNotParse($parser, '{}', 'Expected entity but got eof on line 1.'); // should not parse a named graph $this->shouldNotParse($parser, ' {}', 'Expected entity but got { on line 1.'); // should not parse a named graph with the GRAPH keyword $this->shouldNotParse($parser, 'GRAPH {}', 'Expected entity but got GRAPH on line 1.'); // should not parse a quad $this->shouldNotParse($parser, ' .', 'Expected punctuation to follow "c" on line 1.'); // allows a blank node label in predicate position $this->shouldParse($parser, ' _:b .', ['a', '_:b0_b', 'c']); // should parse a variable $this->shouldParse($parser, '?a ?b ?c.', ['?a', '?b', '?c']); // should parse a simple equality $this->shouldParse($parser, ' = .', ['a', 'http://www.w3.org/2002/07/owl#sameAs', 'b']); // should parse a simple right implication $this->shouldParse($parser, ' => .', ['a', 'http://www.w3.org/2000/10/swap/log#implies', 'b']); // should parse a simple left implication $this->shouldParse($parser, ' <= .', ['b', 'http://www.w3.org/2000/10/swap/log#implies', 'a']); // should parse a right implication between one-triple graphs $this->shouldParse($parser, '{ ?a ?b . } => { ?a }.', ['_:b0', 'http://www.w3.org/2000/10/swap/log#implies', '_:b1'], ['?a', '?b', 'c', '_:b0'], ['d', 'e', '?a', '_:b1']); // should parse a right implication between two-triple graphs $this->shouldParse($parser, '{ ?a ?b . . } => { ?a, }.', ['_:b0', 'http://www.w3.org/2000/10/swap/log#implies', '_:b1'], ['?a', '?b', 'c', '_:b0'], ['d', 'e', 'f', '_:b0'], ['d', 'e', '?a', '_:b1'], ['d', 'e', 'f', '_:b1']); // should parse a left implication between one-triple graphs $this->shouldParse($parser, '{ ?a ?b . } <= { ?a }.', ['_:b1', 'http://www.w3.org/2000/10/swap/log#implies', '_:b0'], ['?a', '?b', 'c', '_:b0'], ['d', 'e', '?a', '_:b1']); // should parse a left implication between two-triple graphs $this->shouldParse($parser, '{ ?a ?b . . } <= { ?a, }.', ['_:b1', 'http://www.w3.org/2000/10/swap/log#implies', '_:b0'], ['?a', '?b', 'c', '_:b0'], ['d', 'e', 'f', '_:b0'], ['d', 'e', '?a', '_:b1'], ['d', 'e', 'f', '_:b1']); // should parse an equality of one-triple graphs $this->shouldParse($parser, '{ ?a ?b . } = { ?a }.', ['_:b0', 'http://www.w3.org/2002/07/owl#sameAs', '_:b1'], ['?a', '?b', 'c', '_:b0'], ['d', 'e', '?a', '_:b1']); // should parse an equality of two-triple graphs $this->shouldParse($parser, '{ ?a ?b . . } = { ?a, }.', ['_:b0', 'http://www.w3.org/2002/07/owl#sameAs', '_:b1'], ['?a', '?b', 'c', '_:b0'], ['d', 'e', 'f', '_:b0'], ['d', 'e', '?a', '_:b1'], ['d', 'e', 'f', '_:b1']); // should parse nested implication graphs $this->shouldParse($parser, '{ { ?a ?b ?c }<={ ?d ?e ?f }. } <= { { ?g ?h ?i } => { ?j ?k ?l } }.', ['_:b3', 'http://www.w3.org/2000/10/swap/log#implies', '_:b0'], ['_:b2', 'http://www.w3.org/2000/10/swap/log#implies', '_:b1', '_:b0'], ['?a', '?b', '?c', '_:b1'], ['?d', '?e', '?f', '_:b2'], ['_:b4', 'http://www.w3.org/2000/10/swap/log#implies', '_:b5', '_:b3'], ['?g', '?h', '?i', '_:b4'], ['?j', '?k', '?l', '_:b5']); // should not reuse identifiers of blank nodes within and outside of formulas $this->shouldParse($parser, '_:a _:b _:c. { _:a _:b _:c } => { { _:a _:b _:c } => { _:a _:b _:c } }.', ['_:b0_a', '_:b0_b', '_:b0_c'], ['_:b0', 'http://www.w3.org/2000/10/swap/log#implies', '_:b1', ''], ['_:b0.a', '_:b0.b', '_:b0.c', '_:b0'], ['_:b2', 'http://www.w3.org/2000/10/swap/log#implies', '_:b3', '_:b1'], ['_:b2.a', '_:b2.b', '_:b2.c', '_:b2'], ['_:b3.a', '_:b3.b', '_:b3.c', '_:b3']); // should parse a @forSome statement $this->shouldParse($parser, '@forSome . .', ['_:b0', '_:b0', '_:b0']); // should parse a @forSome statement with multiple entities $this->shouldParse($parser, '@prefix a: . @base . @forSome a:x, , a:z. a:x a:z.', ['_:b0', '_:b1', '_:b2']); // should not parse a @forSome statement with an invalid prefix $this->shouldNotParse($parser, '@forSome a:b.', 'Undefined prefix "a:" on line 1.'); // should not parse a @forSome statement with a blank node $this->shouldNotParse($parser, '@forSome _:a.', 'Unexpected blank on line 1.'); // should not parse a @forSome statement with a variable $this->shouldNotParse($parser, '@forSome ?a.', 'Unexpected var on line 1.'); // should correctly scope @forSome statements $this->shouldParse($parser, '@forSome . { @forSome . . }. .', ['_:b0', '_:b0', '_:b1'], ['_:b2', '_:b2', '_:b2', '_:b1'], ['_:b0', '_:b0', '_:b0']); // should parse a @forAll statement $this->shouldParse($parser, '@forAll . .', ['?b-0', '?b-0', '?b-0']); // should parse a @forAll statement with multiple entities $this->shouldParse($parser, '@prefix a: . @base . @forAll a:x, , a:z. a:x a:z.', ['?b-0', '?b-1', '?b-2']); // should not parse a @forAll statement with an invalid prefix $this->shouldNotParse($parser, '@forAll a:b.', 'Undefined prefix "a:" on line 1.'); // should not parse a @forAll statement with a blank node $this->shouldNotParse($parser, '@forAll _:a.', 'Unexpected blank on line 1.'); // should not parse a @forAll statement with a variable $this->shouldNotParse($parser, '@forAll ?a.', 'Unexpected var on line 1.'); // should correctly scope @forAll statements $this->shouldParse($parser, '@forAll . { @forAll . . }. .', ['?b-0', '?b-0', '_:b1'], ['?b-2', '?b-2', '?b-2', '_:b1'], ['?b-0', '?b-0', '?b-0']); // should parse a ! path of length 2 as subject $this->shouldParse($parser, '@prefix : . @prefix fam: .'. ':joe!fam:mother a fam:Person.', ['ex:joe', 'f:mother', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person']); // should parse a ! path of length 4 as subject $this->shouldParse($parser, '@prefix : . @prefix fam: . @prefix loc: .'. ':joe!fam:mother!loc:office!loc:zip loc:code 1234.', ['ex:joe', 'f:mother', '_:b0'], ['_:b0', 'l:office', '_:b1'], ['_:b1', 'l:zip', '_:b2'], ['_:b2', 'l:code', '"1234"^^http://www.w3.org/2001/XMLSchema#integer']); // should parse a ! path of length 2 as object $this->shouldParse($parser, '@prefix : . @prefix fam: .'. ' :joe!fam:mother.', ['x', 'is', '_:b0'], ['ex:joe', 'f:mother', '_:b0']); // should parse a ! path of length 4 as object $this->shouldParse($parser, '@prefix : . @prefix fam: . @prefix loc: .'. ' :joe!fam:mother!loc:office!loc:zip.', ['x', 'is', '_:b2'], ['ex:joe', 'f:mother', '_:b0'], ['_:b0', 'l:office', '_:b1'], ['_:b1', 'l:zip', '_:b2']); // should parse a ^ path of length 2 as subject $this->shouldParse($parser, '@prefix : . @prefix fam: .'. ':joe^fam:son a fam:Person.', ['_:b0', 'f:son', 'ex:joe'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person']); // should parse a ^ path of length 4 as subject $this->shouldParse($parser, '@prefix : . @prefix fam: .'. ':joe^fam:son^fam:sister^fam:mother a fam:Person.', ['_:b0', 'f:son', 'ex:joe'], ['_:b1', 'f:sister', '_:b0'], ['_:b2', 'f:mother', '_:b1'], ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person']); // should parse a ^ path of length 2 as object $this->shouldParse($parser, '@prefix : . @prefix fam: .'. ' :joe^fam:son.', ['x', 'is', '_:b0'], ['_:b0', 'f:son', 'ex:joe']); // should parse a ^ path of length 4 as object $this->shouldParse($parser, '@prefix : . @prefix fam: .'. ' :joe^fam:son^fam:sister^fam:mother.', ['x', 'is', '_:b2'], ['_:b0', 'f:son', 'ex:joe'], ['_:b1', 'f:sister', '_:b0'], ['_:b2', 'f:mother', '_:b1']); // should parse mixed !/^ paths as subject $this->shouldParse($parser, '@prefix : . @prefix fam: .'. ':joe!fam:mother^fam:mother a fam:Person.', ['ex:joe', 'f:mother', '_:b0'], ['_:b1', 'f:mother', '_:b0'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person']); // should parse mixed !/^ paths as object $this->shouldParse($parser, '@prefix : . @prefix fam: .'. ' :joe!fam:mother^fam:mother.', ['x', 'is', '_:b1'], ['ex:joe', 'f:mother', '_:b0'], ['_:b1', 'f:mother', '_:b0']); // should parse a ! path in a blank node as subject $this->shouldParse($parser, '@prefix : . @prefix fam: .'. '[fam:knows :joe!fam:mother] a fam:Person.', ['_:b0', 'f:knows', '_:b1'], ['ex:joe', 'f:mother', '_:b1'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person']); // should parse a ! path in a blank node as object $this->shouldParse($parser, '@prefix : . @prefix fam: .'. ' [fam:knows :joe!fam:mother].', ['x', 'is', '_:b0'], ['_:b0', 'f:knows', '_:b1'], ['ex:joe', 'f:mother', '_:b1']); // should parse a ^ path in a blank node as subject $this->shouldParse($parser, '@prefix : . @prefix fam: .'. '[fam:knows :joe^fam:son] a fam:Person.', ['_:b0', 'f:knows', '_:b1'], ['_:b1', 'f:son', 'ex:joe'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person']); // should parse a ^ path in a blank node as object $this->shouldParse($parser, '@prefix : . @prefix fam: .'. ' [fam:knows :joe^fam:son].', ['x', 'is', '_:b0'], ['_:b0', 'f:knows', '_:b1'], ['_:b1', 'f:son', 'ex:joe']); // should parse a ! path in a list as subject $this->shouldParse($parser, '@prefix : . @prefix fam: .'. '( :joe!fam:mother ) a :List.', ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'ex:List'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'x'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b1'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b2'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b3'], ['_:b3', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'y'], ['_:b3', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], ['ex:joe', 'f:mother', '_:b2']); // should parse a ! path in a list as object $this->shouldParse($parser, '@prefix : . @prefix fam: .'. ' ( :joe!fam:mother ).', ['l', 'is', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'x'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b1'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b2'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b3'], ['_:b3', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'y'], ['_:b3', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], ['ex:joe', 'f:mother', '_:b2']); // should parse a ^ path in a list as subject $this->shouldParse($parser, '@prefix : . @prefix fam: .'. '( :joe^fam:son ) a :List.', ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'ex:List'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'x'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b1'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b2'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b3'], ['_:b3', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'y'], ['_:b3', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], ['_:b2', 'f:son', 'ex:joe']); // should parse a ^ path in a list as object $this->shouldParse($parser, '@prefix : . @prefix fam: .'. ' ( :joe^fam:son ).', ['l', 'is', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'x'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b1'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b2'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b3'], ['_:b3', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'y'], ['_:b3', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], ['_:b2', 'f:son', 'ex:joe']); // should not parse an invalid ! path $this->shouldNotParse($parser, '!"invalid" ', 'Expected entity but got literal on line 1.'); // should not parse an invalid ^ path $this->shouldNotParse($parser, '^"invalid" ', 'Expected entity but got literal on line 1.'); } public function testN3ExplicitQuantifiers(): void { $parser = function () { return new TriGParser(['format' => 'N3', 'explicitQuantifiers' => true]); }; // should parse a @forSome statement $this->shouldParse($parser, '@forSome . .', ['', 'http://www.w3.org/2000/10/swap/reify#forSome', '_:b0', 'urn:n3:quantifiers'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'x', 'urn:n3:quantifiers'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', 'urn:n3:quantifiers'], ['x', 'x', 'x']); // should parse a @forSome statement with multiple entities $this->shouldParse($parser, '@prefix a: . @base . @forSome a:x, , a:z. a:x a:z.', ['', 'http://www.w3.org/2000/10/swap/reify#forSome', '_:b0', 'urn:n3:quantifiers'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'a:x', 'urn:n3:quantifiers'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b1', 'urn:n3:quantifiers'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'b:y', 'urn:n3:quantifiers'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b2', 'urn:n3:quantifiers'], ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'a:z', 'urn:n3:quantifiers'], ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', 'urn:n3:quantifiers'], ['a:x', 'b:y', 'a:z']); // should correctly scope @forSome statements $this->shouldParse($parser, '@forSome . { @forSome . . }. .', ['', 'http://www.w3.org/2000/10/swap/reify#forSome', '_:b0', 'urn:n3:quantifiers'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'x', 'urn:n3:quantifiers'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', 'urn:n3:quantifiers'], ['x', 'x', '_:b1'], ['_:b1', 'http://www.w3.org/2000/10/swap/reify#forSome', '_:b2', 'urn:n3:quantifiers'], ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'x', 'urn:n3:quantifiers'], ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', 'urn:n3:quantifiers'], ['x', 'x', 'x', '_:b1'], ['x', 'x', 'x']); // should parse a @forAll statement $this->shouldParse($parser, '@forAll . .', ['', 'http://www.w3.org/2000/10/swap/reify#forAll', '_:b0', 'urn:n3:quantifiers'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'x', 'urn:n3:quantifiers'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', 'urn:n3:quantifiers'], ['x', 'x', 'x']); // should parse a @forAll statement with multiple entities $this->shouldParse($parser, '@prefix a: . @base . @forAll a:x, , a:z. a:x a:z.', ['', 'http://www.w3.org/2000/10/swap/reify#forAll', '_:b0', 'urn:n3:quantifiers'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'a:x', 'urn:n3:quantifiers'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b1', 'urn:n3:quantifiers'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'b:y', 'urn:n3:quantifiers'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b2', 'urn:n3:quantifiers'], ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'a:z', 'urn:n3:quantifiers'], ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', 'urn:n3:quantifiers'], ['a:x', 'b:y', 'a:z']); // should correctly scope @forAll statements $this->shouldParse($parser, '@forAll . { @forAll . . }. .', ['', 'http://www.w3.org/2000/10/swap/reify#forAll', '_:b0', 'urn:n3:quantifiers'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'x', 'urn:n3:quantifiers'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', 'urn:n3:quantifiers'], ['x', 'x', '_:b1'], ['_:b1', 'http://www.w3.org/2000/10/swap/reify#forAll', '_:b2', 'urn:n3:quantifiers'], ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'x', 'urn:n3:quantifiers'], ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', 'urn:n3:quantifiers'], ['x', 'x', 'x', '_:b1'], ['x', 'x', 'x']); } public function testResolve(): void { // IRI resolution // RFC3986 normal examples $this->itShouldResolve('http://a/bb/ccc/d;p?q', 'g:h', 'g:h'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', 'g', 'http://a/bb/ccc/g'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', './g', 'http://a/bb/ccc/g'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', 'g/', 'http://a/bb/ccc/g/'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', '/g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', '//g', 'http://g'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', '?y', 'http://a/bb/ccc/d;p?y'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', 'g?y', 'http://a/bb/ccc/g?y'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', '#s', 'http://a/bb/ccc/d;p?q#s'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', 'g#s', 'http://a/bb/ccc/g#s'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', 'g?y#s', 'http://a/bb/ccc/g?y#s'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', ';x', 'http://a/bb/ccc/;x'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', 'g;x', 'http://a/bb/ccc/g;x'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', 'g;x?y#s', 'http://a/bb/ccc/g;x?y#s'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', '', 'http://a/bb/ccc/d;p?q'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', '.', 'http://a/bb/ccc/'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', './', 'http://a/bb/ccc/'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', '..', 'http://a/bb/'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', '../', 'http://a/bb/'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', '../g', 'http://a/bb/g'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', '../..', 'http://a/'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', '../../', 'http://a/'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', '../../g', 'http://a/g'); // RFC3986 abnormal examples $this->itShouldResolve('http://a/bb/ccc/d;p?q', '../../../g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', '../../../../g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', '/./g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', '/../g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', 'g.', 'http://a/bb/ccc/g.'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', '.g', 'http://a/bb/ccc/.g'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', 'g..', 'http://a/bb/ccc/g..'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', '..g', 'http://a/bb/ccc/..g'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', './../g', 'http://a/bb/g'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', './g/.', 'http://a/bb/ccc/g/'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', 'g/./h', 'http://a/bb/ccc/g/h'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', 'g/../h', 'http://a/bb/ccc/h'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', 'g;x=1/./y', 'http://a/bb/ccc/g;x=1/y'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', 'g;x=1/../y', 'http://a/bb/ccc/y'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', 'g?y/./x', 'http://a/bb/ccc/g?y/./x'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', 'g?y/../x', 'http://a/bb/ccc/g?y/../x'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', 'g#s/./x', 'http://a/bb/ccc/g#s/./x'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', 'g#s/../x', 'http://a/bb/ccc/g#s/../x'); $this->itShouldResolve('http://a/bb/ccc/d;p?q', 'http:g', 'http:g'); // RFC3986 normal examples with trailing slash in base IRI $this->itShouldResolve('http://a/bb/ccc/d/', 'g:h', 'g:h'); $this->itShouldResolve('http://a/bb/ccc/d/', 'g', 'http://a/bb/ccc/d/g'); $this->itShouldResolve('http://a/bb/ccc/d/', './g', 'http://a/bb/ccc/d/g'); $this->itShouldResolve('http://a/bb/ccc/d/', 'g/', 'http://a/bb/ccc/d/g/'); $this->itShouldResolve('http://a/bb/ccc/d/', '/g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/d/', '//g', 'http://g'); $this->itShouldResolve('http://a/bb/ccc/d/', '?y', 'http://a/bb/ccc/d/?y'); $this->itShouldResolve('http://a/bb/ccc/d/', 'g?y', 'http://a/bb/ccc/d/g?y'); $this->itShouldResolve('http://a/bb/ccc/d/', '#s', 'http://a/bb/ccc/d/#s'); $this->itShouldResolve('http://a/bb/ccc/d/', 'g#s', 'http://a/bb/ccc/d/g#s'); $this->itShouldResolve('http://a/bb/ccc/d/', 'g?y#s', 'http://a/bb/ccc/d/g?y#s'); $this->itShouldResolve('http://a/bb/ccc/d/', ';x', 'http://a/bb/ccc/d/;x'); $this->itShouldResolve('http://a/bb/ccc/d/', 'g;x', 'http://a/bb/ccc/d/g;x'); $this->itShouldResolve('http://a/bb/ccc/d/', 'g;x?y#s', 'http://a/bb/ccc/d/g;x?y#s'); $this->itShouldResolve('http://a/bb/ccc/d/', '', 'http://a/bb/ccc/d/'); $this->itShouldResolve('http://a/bb/ccc/d/', '.', 'http://a/bb/ccc/d/'); $this->itShouldResolve('http://a/bb/ccc/d/', './', 'http://a/bb/ccc/d/'); $this->itShouldResolve('http://a/bb/ccc/d/', '..', 'http://a/bb/ccc/'); $this->itShouldResolve('http://a/bb/ccc/d/', '../', 'http://a/bb/ccc/'); $this->itShouldResolve('http://a/bb/ccc/d/', '../g', 'http://a/bb/ccc/g'); $this->itShouldResolve('http://a/bb/ccc/d/', '../..', 'http://a/bb/'); $this->itShouldResolve('http://a/bb/ccc/d/', '../../', 'http://a/bb/'); $this->itShouldResolve('http://a/bb/ccc/d/', '../../g', 'http://a/bb/g'); // RFC3986 abnormal examples with trailing slash in base IRI $this->itShouldResolve('http://a/bb/ccc/d/', '../../../g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/d/', '../../../../g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/d/', '/./g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/d/', '/../g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/d/', 'g.', 'http://a/bb/ccc/d/g.'); $this->itShouldResolve('http://a/bb/ccc/d/', '.g', 'http://a/bb/ccc/d/.g'); $this->itShouldResolve('http://a/bb/ccc/d/', 'g..', 'http://a/bb/ccc/d/g..'); $this->itShouldResolve('http://a/bb/ccc/d/', '..g', 'http://a/bb/ccc/d/..g'); $this->itShouldResolve('http://a/bb/ccc/d/', './../g', 'http://a/bb/ccc/g'); $this->itShouldResolve('http://a/bb/ccc/d/', './g/.', 'http://a/bb/ccc/d/g/'); $this->itShouldResolve('http://a/bb/ccc/d/', 'g/./h', 'http://a/bb/ccc/d/g/h'); $this->itShouldResolve('http://a/bb/ccc/d/', 'g/../h', 'http://a/bb/ccc/d/h'); $this->itShouldResolve('http://a/bb/ccc/d/', 'g;x=1/./y', 'http://a/bb/ccc/d/g;x=1/y'); $this->itShouldResolve('http://a/bb/ccc/d/', 'g;x=1/../y', 'http://a/bb/ccc/d/y'); $this->itShouldResolve('http://a/bb/ccc/d/', 'g?y/./x', 'http://a/bb/ccc/d/g?y/./x'); $this->itShouldResolve('http://a/bb/ccc/d/', 'g?y/../x', 'http://a/bb/ccc/d/g?y/../x'); $this->itShouldResolve('http://a/bb/ccc/d/', 'g#s/./x', 'http://a/bb/ccc/d/g#s/./x'); $this->itShouldResolve('http://a/bb/ccc/d/', 'g#s/../x', 'http://a/bb/ccc/d/g#s/../x'); $this->itShouldResolve('http://a/bb/ccc/d/', 'http:g', 'http:g'); // RFC3986 normal examples with /. in the base IRI $this->itShouldResolve('http://a/bb/ccc/./d;p?q', 'g:h', 'g:h'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', 'g', 'http://a/bb/ccc/g'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', './g', 'http://a/bb/ccc/g'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', 'g/', 'http://a/bb/ccc/g/'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', '/g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', '//g', 'http://g'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', '?y', 'http://a/bb/ccc/./d;p?y'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', 'g?y', 'http://a/bb/ccc/g?y'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', '#s', 'http://a/bb/ccc/./d;p?q#s'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', 'g#s', 'http://a/bb/ccc/g#s'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', 'g?y#s', 'http://a/bb/ccc/g?y#s'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', ';x', 'http://a/bb/ccc/;x'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', 'g;x', 'http://a/bb/ccc/g;x'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', 'g;x?y#s', 'http://a/bb/ccc/g;x?y#s'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', '', 'http://a/bb/ccc/./d;p?q'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', '.', 'http://a/bb/ccc/'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', './', 'http://a/bb/ccc/'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', '..', 'http://a/bb/'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', '../', 'http://a/bb/'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', '../g', 'http://a/bb/g'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', '../..', 'http://a/'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', '../../', 'http://a/'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', '../../g', 'http://a/g'); // RFC3986 abnormal examples with /. in the base IRI $this->itShouldResolve('http://a/bb/ccc/./d;p?q', '../../../g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', '../../../../g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', '/./g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', '/../g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', 'g.', 'http://a/bb/ccc/g.'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', '.g', 'http://a/bb/ccc/.g'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', 'g..', 'http://a/bb/ccc/g..'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', '..g', 'http://a/bb/ccc/..g'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', './../g', 'http://a/bb/g'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', './g/.', 'http://a/bb/ccc/g/'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', 'g/./h', 'http://a/bb/ccc/g/h'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', 'g/../h', 'http://a/bb/ccc/h'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', 'g;x=1/./y', 'http://a/bb/ccc/g;x=1/y'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', 'g;x=1/../y', 'http://a/bb/ccc/y'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', 'g?y/./x', 'http://a/bb/ccc/g?y/./x'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', 'g?y/../x', 'http://a/bb/ccc/g?y/../x'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', 'g#s/./x', 'http://a/bb/ccc/g#s/./x'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', 'g#s/../x', 'http://a/bb/ccc/g#s/../x'); $this->itShouldResolve('http://a/bb/ccc/./d;p?q', 'http:g', 'http:g'); // RFC3986 normal examples with /.. in the base IRI $this->itShouldResolve('http://a/bb/ccc/../d;p?q', 'g:h', 'g:h'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', 'g', 'http://a/bb/g'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', './g', 'http://a/bb/g'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', 'g/', 'http://a/bb/g/'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', '/g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', '//g', 'http://g'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', '?y', 'http://a/bb/ccc/../d;p?y'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', 'g?y', 'http://a/bb/g?y'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', '#s', 'http://a/bb/ccc/../d;p?q#s'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', 'g#s', 'http://a/bb/g#s'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', 'g?y#s', 'http://a/bb/g?y#s'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', ';x', 'http://a/bb/;x'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', 'g;x', 'http://a/bb/g;x'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', 'g;x?y#s', 'http://a/bb/g;x?y#s'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', '', 'http://a/bb/ccc/../d;p?q'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', '.', 'http://a/bb/'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', './', 'http://a/bb/'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', '..', 'http://a/'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', '../', 'http://a/'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', '../g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', '../..', 'http://a/'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', '../../', 'http://a/'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', '../../g', 'http://a/g'); // RFC3986 abnormal examples with /.. in the base IRI $this->itShouldResolve('http://a/bb/ccc/../d;p?q', '../../../g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', '../../../../g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', '/./g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', '/../g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', 'g.', 'http://a/bb/g.'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', '.g', 'http://a/bb/.g'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', 'g..', 'http://a/bb/g..'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', '..g', 'http://a/bb/..g'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', './../g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', './g/.', 'http://a/bb/g/'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', 'g/./h', 'http://a/bb/g/h'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', 'g/../h', 'http://a/bb/h'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', 'g;x=1/./y', 'http://a/bb/g;x=1/y'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', 'g;x=1/../y', 'http://a/bb/y'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', 'g?y/./x', 'http://a/bb/g?y/./x'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', 'g?y/../x', 'http://a/bb/g?y/../x'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', 'g#s/./x', 'http://a/bb/g#s/./x'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', 'g#s/../x', 'http://a/bb/g#s/../x'); $this->itShouldResolve('http://a/bb/ccc/../d;p?q', 'http:g', 'http:g'); // RFC3986 normal examples with trailing /. in the base IRI $this->itShouldResolve('http://a/bb/ccc/.', 'g:h', 'g:h'); $this->itShouldResolve('http://a/bb/ccc/.', 'g', 'http://a/bb/ccc/g'); $this->itShouldResolve('http://a/bb/ccc/.', './g', 'http://a/bb/ccc/g'); $this->itShouldResolve('http://a/bb/ccc/.', 'g/', 'http://a/bb/ccc/g/'); $this->itShouldResolve('http://a/bb/ccc/.', '/g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/.', '//g', 'http://g'); $this->itShouldResolve('http://a/bb/ccc/.', '?y', 'http://a/bb/ccc/.?y'); $this->itShouldResolve('http://a/bb/ccc/.', 'g?y', 'http://a/bb/ccc/g?y'); $this->itShouldResolve('http://a/bb/ccc/.', '#s', 'http://a/bb/ccc/.#s'); $this->itShouldResolve('http://a/bb/ccc/.', 'g#s', 'http://a/bb/ccc/g#s'); $this->itShouldResolve('http://a/bb/ccc/.', 'g?y#s', 'http://a/bb/ccc/g?y#s'); $this->itShouldResolve('http://a/bb/ccc/.', ';x', 'http://a/bb/ccc/;x'); $this->itShouldResolve('http://a/bb/ccc/.', 'g;x', 'http://a/bb/ccc/g;x'); $this->itShouldResolve('http://a/bb/ccc/.', 'g;x?y#s', 'http://a/bb/ccc/g;x?y#s'); $this->itShouldResolve('http://a/bb/ccc/.', '', 'http://a/bb/ccc/.'); $this->itShouldResolve('http://a/bb/ccc/.', '.', 'http://a/bb/ccc/'); $this->itShouldResolve('http://a/bb/ccc/.', './', 'http://a/bb/ccc/'); $this->itShouldResolve('http://a/bb/ccc/.', '..', 'http://a/bb/'); $this->itShouldResolve('http://a/bb/ccc/.', '../', 'http://a/bb/'); $this->itShouldResolve('http://a/bb/ccc/.', '../g', 'http://a/bb/g'); $this->itShouldResolve('http://a/bb/ccc/.', '../..', 'http://a/'); $this->itShouldResolve('http://a/bb/ccc/.', '../../', 'http://a/'); $this->itShouldResolve('http://a/bb/ccc/.', '../../g', 'http://a/g'); // RFC3986 abnormal examples with trailing /. in the base IRI $this->itShouldResolve('http://a/bb/ccc/.', '../../../g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/.', '../../../../g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/.', '/./g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/.', '/../g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/.', 'g.', 'http://a/bb/ccc/g.'); $this->itShouldResolve('http://a/bb/ccc/.', '.g', 'http://a/bb/ccc/.g'); $this->itShouldResolve('http://a/bb/ccc/.', 'g..', 'http://a/bb/ccc/g..'); $this->itShouldResolve('http://a/bb/ccc/.', '..g', 'http://a/bb/ccc/..g'); $this->itShouldResolve('http://a/bb/ccc/.', './../g', 'http://a/bb/g'); $this->itShouldResolve('http://a/bb/ccc/.', './g/.', 'http://a/bb/ccc/g/'); $this->itShouldResolve('http://a/bb/ccc/.', 'g/./h', 'http://a/bb/ccc/g/h'); $this->itShouldResolve('http://a/bb/ccc/.', 'g/../h', 'http://a/bb/ccc/h'); $this->itShouldResolve('http://a/bb/ccc/.', 'g;x=1/./y', 'http://a/bb/ccc/g;x=1/y'); $this->itShouldResolve('http://a/bb/ccc/.', 'g;x=1/../y', 'http://a/bb/ccc/y'); $this->itShouldResolve('http://a/bb/ccc/.', 'g?y/./x', 'http://a/bb/ccc/g?y/./x'); $this->itShouldResolve('http://a/bb/ccc/.', 'g?y/../x', 'http://a/bb/ccc/g?y/../x'); $this->itShouldResolve('http://a/bb/ccc/.', 'g#s/./x', 'http://a/bb/ccc/g#s/./x'); $this->itShouldResolve('http://a/bb/ccc/.', 'g#s/../x', 'http://a/bb/ccc/g#s/../x'); $this->itShouldResolve('http://a/bb/ccc/.', 'http:g', 'http:g'); // RFC3986 normal examples with trailing /.. in the base IRI $this->itShouldResolve('http://a/bb/ccc/..', 'g:h', 'g:h'); $this->itShouldResolve('http://a/bb/ccc/..', 'g', 'http://a/bb/ccc/g'); $this->itShouldResolve('http://a/bb/ccc/..', './g', 'http://a/bb/ccc/g'); $this->itShouldResolve('http://a/bb/ccc/..', 'g/', 'http://a/bb/ccc/g/'); $this->itShouldResolve('http://a/bb/ccc/..', '/g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/..', '//g', 'http://g'); $this->itShouldResolve('http://a/bb/ccc/..', '?y', 'http://a/bb/ccc/..?y'); $this->itShouldResolve('http://a/bb/ccc/..', 'g?y', 'http://a/bb/ccc/g?y'); $this->itShouldResolve('http://a/bb/ccc/..', '#s', 'http://a/bb/ccc/..#s'); $this->itShouldResolve('http://a/bb/ccc/..', 'g#s', 'http://a/bb/ccc/g#s'); $this->itShouldResolve('http://a/bb/ccc/..', 'g?y#s', 'http://a/bb/ccc/g?y#s'); $this->itShouldResolve('http://a/bb/ccc/..', ';x', 'http://a/bb/ccc/;x'); $this->itShouldResolve('http://a/bb/ccc/..', 'g;x', 'http://a/bb/ccc/g;x'); $this->itShouldResolve('http://a/bb/ccc/..', 'g;x?y#s', 'http://a/bb/ccc/g;x?y#s'); $this->itShouldResolve('http://a/bb/ccc/..', '', 'http://a/bb/ccc/..'); $this->itShouldResolve('http://a/bb/ccc/..', '.', 'http://a/bb/ccc/'); $this->itShouldResolve('http://a/bb/ccc/..', './', 'http://a/bb/ccc/'); $this->itShouldResolve('http://a/bb/ccc/..', '..', 'http://a/bb/'); $this->itShouldResolve('http://a/bb/ccc/..', '../', 'http://a/bb/'); $this->itShouldResolve('http://a/bb/ccc/..', '../g', 'http://a/bb/g'); $this->itShouldResolve('http://a/bb/ccc/..', '../..', 'http://a/'); $this->itShouldResolve('http://a/bb/ccc/..', '../../', 'http://a/'); $this->itShouldResolve('http://a/bb/ccc/..', '../../g', 'http://a/g'); // RFC3986 abnormal examples with trailing /.. in the base IRI $this->itShouldResolve('http://a/bb/ccc/..', '../../../g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/..', '../../../../g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/..', '/./g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/..', '/../g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/..', 'g.', 'http://a/bb/ccc/g.'); $this->itShouldResolve('http://a/bb/ccc/..', '.g', 'http://a/bb/ccc/.g'); $this->itShouldResolve('http://a/bb/ccc/..', 'g..', 'http://a/bb/ccc/g..'); $this->itShouldResolve('http://a/bb/ccc/..', '..g', 'http://a/bb/ccc/..g'); $this->itShouldResolve('http://a/bb/ccc/..', './../g', 'http://a/bb/g'); $this->itShouldResolve('http://a/bb/ccc/..', './g/.', 'http://a/bb/ccc/g/'); $this->itShouldResolve('http://a/bb/ccc/..', 'g/./h', 'http://a/bb/ccc/g/h'); $this->itShouldResolve('http://a/bb/ccc/..', 'g/../h', 'http://a/bb/ccc/h'); $this->itShouldResolve('http://a/bb/ccc/..', 'g;x=1/./y', 'http://a/bb/ccc/g;x=1/y'); $this->itShouldResolve('http://a/bb/ccc/..', 'g;x=1/../y', 'http://a/bb/ccc/y'); $this->itShouldResolve('http://a/bb/ccc/..', 'g?y/./x', 'http://a/bb/ccc/g?y/./x'); $this->itShouldResolve('http://a/bb/ccc/..', 'g?y/../x', 'http://a/bb/ccc/g?y/../x'); $this->itShouldResolve('http://a/bb/ccc/..', 'g#s/./x', 'http://a/bb/ccc/g#s/./x'); $this->itShouldResolve('http://a/bb/ccc/..', 'g#s/../x', 'http://a/bb/ccc/g#s/../x'); $this->itShouldResolve('http://a/bb/ccc/..', 'http:g', 'http:g'); // RFC3986 normal examples with fragment in base IRI $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', 'g:h', 'g:h'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', 'g', 'http://a/bb/ccc/g'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', './g', 'http://a/bb/ccc/g'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', 'g/', 'http://a/bb/ccc/g/'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', '/g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', '//g', 'http://g'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', '?y', 'http://a/bb/ccc/d;p?y'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', 'g?y', 'http://a/bb/ccc/g?y'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', '#s', 'http://a/bb/ccc/d;p?q#s'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', 'g#s', 'http://a/bb/ccc/g#s'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', 'g?y#s', 'http://a/bb/ccc/g?y#s'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', ';x', 'http://a/bb/ccc/;x'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', 'g;x', 'http://a/bb/ccc/g;x'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', 'g;x?y#s', 'http://a/bb/ccc/g;x?y#s'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', '', 'http://a/bb/ccc/d;p?q'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', '.', 'http://a/bb/ccc/'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', './', 'http://a/bb/ccc/'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', '..', 'http://a/bb/'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', '../', 'http://a/bb/'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', '../g', 'http://a/bb/g'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', '../..', 'http://a/'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', '../../', 'http://a/'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', '../../g', 'http://a/g'); // RFC3986 abnormal examples with fragment in base IRI $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', '../../../g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', '../../../../g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', '/./g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', '/../g', 'http://a/g'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', 'g.', 'http://a/bb/ccc/g.'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', '.g', 'http://a/bb/ccc/.g'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', 'g..', 'http://a/bb/ccc/g..'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', '..g', 'http://a/bb/ccc/..g'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', './../g', 'http://a/bb/g'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', './g/.', 'http://a/bb/ccc/g/'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', 'g/./h', 'http://a/bb/ccc/g/h'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', 'g/../h', 'http://a/bb/ccc/h'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', 'g;x=1/./y', 'http://a/bb/ccc/g;x=1/y'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', 'g;x=1/../y', 'http://a/bb/ccc/y'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', 'g?y/./x', 'http://a/bb/ccc/g?y/./x'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', 'g?y/../x', 'http://a/bb/ccc/g?y/../x'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', 'g#s/./x', 'http://a/bb/ccc/g#s/./x'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', 'g#s/../x', 'http://a/bb/ccc/g#s/../x'); $this->itShouldResolve('http://a/bb/ccc/d;p?q#f', 'http:g', 'http:g'); // RFC3986 normal examples with file path $this->itShouldResolve('file:///a/bb/ccc/d;p?q', 'g:h', 'g:h'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', 'g', 'file:///a/bb/ccc/g'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', './g', 'file:///a/bb/ccc/g'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', 'g/', 'file:///a/bb/ccc/g/'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', '/g', 'file:///g'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', '//g', 'file://g'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', '?y', 'file:///a/bb/ccc/d;p?y'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', 'g?y', 'file:///a/bb/ccc/g?y'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', '#s', 'file:///a/bb/ccc/d;p?q#s'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', 'g#s', 'file:///a/bb/ccc/g#s'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', 'g?y#s', 'file:///a/bb/ccc/g?y#s'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', ';x', 'file:///a/bb/ccc/;x'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', 'g;x', 'file:///a/bb/ccc/g;x'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', 'g;x?y#s', 'file:///a/bb/ccc/g;x?y#s'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', '', 'file:///a/bb/ccc/d;p?q'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', '.', 'file:///a/bb/ccc/'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', './', 'file:///a/bb/ccc/'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', '..', 'file:///a/bb/'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', '../', 'file:///a/bb/'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', '../g', 'file:///a/bb/g'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', '../..', 'file:///a/'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', '../../', 'file:///a/'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', '../../g', 'file:///a/g'); // RFC3986 abnormal examples with file path $this->itShouldResolve('file:///a/bb/ccc/d;p?q', '../../../g', 'file:///g'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', '../../../../g', 'file:///g'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', '/./g', 'file:///g'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', '/../g', 'file:///g'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', 'g.', 'file:///a/bb/ccc/g.'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', '.g', 'file:///a/bb/ccc/.g'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', 'g..', 'file:///a/bb/ccc/g..'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', '..g', 'file:///a/bb/ccc/..g'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', './../g', 'file:///a/bb/g'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', './g/.', 'file:///a/bb/ccc/g/'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', 'g/./h', 'file:///a/bb/ccc/g/h'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', 'g/../h', 'file:///a/bb/ccc/h'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', 'g;x=1/./y', 'file:///a/bb/ccc/g;x=1/y'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', 'g;x=1/../y', 'file:///a/bb/ccc/y'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', 'g?y/./x', 'file:///a/bb/ccc/g?y/./x'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', 'g?y/../x', 'file:///a/bb/ccc/g?y/../x'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', 'g#s/./x', 'file:///a/bb/ccc/g#s/./x'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', 'g#s/../x', 'file:///a/bb/ccc/g#s/../x'); $this->itShouldResolve('file:///a/bb/ccc/d;p?q', 'http:g', 'http:g'); // additional cases // relative paths ending with '.' $this->itShouldResolve('http://abc/', '.', 'http://abc/'); $this->itShouldResolve('http://abc/def/ghi', '.', 'http://abc/def/'); $this->itShouldResolve('http://abc/def/ghi', '.?a=b', 'http://abc/def/?a=b'); $this->itShouldResolve('http://abc/def/ghi', '.#a=b', 'http://abc/def/#a=b'); // relative paths ending with '..' $this->itShouldResolve('http://abc/', '..', 'http://abc/'); $this->itShouldResolve('http://abc/def/ghi', '..', 'http://abc/'); $this->itShouldResolve('http://abc/def/ghi', '..?a=b', 'http://abc/?a=b'); $this->itShouldResolve('http://abc/def/ghi', '..#a=b', 'http://abc/#a=b'); // base path with empty subpaths (double slashes) $this->itShouldResolve('http://ab//de//ghi', 'xyz', 'http://ab//de//xyz'); $this->itShouldResolve('http://ab//de//ghi', './xyz', 'http://ab//de//xyz'); $this->itShouldResolve('http://ab//de//ghi', '../xyz', 'http://ab//de/xyz'); // base path with colon (possible confusion with scheme) $this->itShouldResolve('http://abc/d:f/ghi', 'xyz', 'http://abc/d:f/xyz'); $this->itShouldResolve('http://abc/d:f/ghi', './xyz', 'http://abc/d:f/xyz'); $this->itShouldResolve('http://abc/d:f/ghi', '../xyz', 'http://abc/xyz'); // base path consisting of '..' and/or '../' sequences $this->itShouldResolve('./', 'abc', '/abc'); $this->itShouldResolve('../', 'abc', '/abc'); $this->itShouldResolve('./././', '././abc', '/abc'); $this->itShouldResolve('../../../', '../../abc', '/abc'); $this->itShouldResolve('.../././', '././abc', '.../abc'); // base path without authority $this->itShouldResolve('a:b:c/', 'def/../', 'a:b:c/'); $this->itShouldResolve('a:b:c', '/def', 'a:/def'); $this->itShouldResolve('a:b/c', '/def', 'a:/def'); $this->itShouldResolve('a:', '/.', 'a:/'); $this->itShouldResolve('a:', '/..', 'a:/'); // base path with slashes in query string $this->itShouldResolve('http://abc/def/ghi?q=xx/yyy/z', 'jjj', 'http://abc/def/jjj'); $this->itShouldResolve('http://abc/def/ghi?q=xx/y?y/z', 'jjj', 'http://abc/def/jjj'); } // https://github.com/pietercolpaert/hardf/issues/37 public function testIssue37(): void { // should throw an error on empty subject/predicate/object $errSuffix = " on line 1 can not be parsed without knowing the the document base IRI.\n". "Please set the document base IRI using the documentIRI parser configuration option.\n". "See https://github.com/pietercolpaert/hardf/#empty-document-base-IRI ."; $this->shouldNotParse('<> .', 'subject' . $errSuffix); $this->shouldNotParse(' <> .', 'predicate' . $errSuffix); $this->shouldNotParse(' <> .', 'object' . $errSuffix); // but should manage with documentIRI being set or @base in the turle $this->shouldParse( "@base .\n". "<> .\n". " <> .\n". " <> .", ['http://base/', 'http://base/b', 'http://base/c'], ['http://base/a', 'http://base/', 'http://base/c'], ['http://base/a', 'http://base/b', 'http://base/']); $parser = function () { return new TriGParser(['documentIRI' => 'http://base/']); }; $this->shouldParse($parser, "<> .\n". " <> .\n". " <> .", ['http://base/', 'http://base/b', 'http://base/c'], ['http://base/a', 'http://base/', 'http://base/c'], ['http://base/a', 'http://base/b', 'http://base/']); } }