defmodule RDF.Turtle.DecoderTest do use ExUnit.Case, async: false doctest RDF.Turtle.Decoder import RDF.Sigils alias RDF.{Turtle, Graph, NS, XSD} use RDF.Vocabulary.Namespace defvocab EX, base_iri: "http://example.org/#", terms: [], strict: false defvocab P, base_iri: "http://www.perceive.net/schemas/relationship/", terms: [], strict: false test "an empty string is deserialized to an empty graph" do assert Turtle.Decoder.decode!("") == Graph.new() assert Turtle.Decoder.decode!(" \n\r\r\n ") == Graph.new() end test "comments" do assert Turtle.Decoder.decode!("# just a comment") == Graph.new() assert Turtle.Decoder.decode!(""" _:1 . # a comment """) == Graph.new({EX.S, EX.p(), RDF.bnode("1")}) assert Turtle.Decoder.decode!(""" # a comment . """) == Graph.new({EX.S, EX.p(), EX.O}) assert Turtle.Decoder.decode!(""" . # a comment """) == Graph.new({EX.S, EX.p(), EX.O}) assert Turtle.Decoder.decode!(""" # Header line 1 # Header line 2 . # 1st comment . # 2nd comment # last comment """) == Graph.new([ {EX.S1, EX.p1(), EX.O1}, {EX.S1, EX.p2(), EX.O2} ]) end test "empty lines" do assert Turtle.Decoder.decode!(""" . """) == Graph.new({EX.spiderman(), P.enemyOf(), EX.green_goblin()}) assert Turtle.Decoder.decode!(""" . """) == Graph.new({EX.spiderman(), P.enemyOf(), EX.green_goblin()}) assert Turtle.Decoder.decode!(""" . . """) == Graph.new([ {EX.S1, EX.p1(), EX.O1}, {EX.S1, EX.p2(), EX.O2} ]) end describe "statements" do test "a N-Triple-style statement" do assert Turtle.Decoder.decode!( " ." ) == Graph.new({EX.Aaron, RDF.type(), EX.Person}) end test "a statement with the 'a' keyword" do assert Turtle.Decoder.decode!(""" a . """) == Graph.new({EX.Aaron, RDF.type(), EX.Person}) end test "multiple N-Triple-style statement" do assert Turtle.Decoder.decode!(""" . . """) == Graph.new([ {EX.S1, EX.p1(), EX.O1}, {EX.S1, EX.p2(), EX.O2} ]) assert Turtle.Decoder.decode!(""" . . . """) == Graph.new([ {EX.S1, EX.p1(), EX.O1}, {EX.S1, EX.p2(), EX.O2}, {EX.S2, EX.p3(), EX.O3} ]) end test "statement with multiple objects" do assert Turtle.Decoder.decode!(""" "baz", 1, true . """) == Graph.new([ {EX.Foo, EX.bar(), "baz"}, {EX.Foo, EX.bar(), 1}, {EX.Foo, EX.bar(), true} ]) end test "statement with multiple predications" do assert Turtle.Decoder.decode!(""" "baz"; 42 . """) == Graph.new([ {EX.Foo, EX.bar(), "baz"}, {EX.Foo, EX.baz(), 42} ]) end end describe "blank node property lists" do test "blank node property list on object position" do assert Turtle.Decoder.decode!(""" [ 42 ] . """) == Graph.new([ {EX.Foo, EX.bar(), RDF.bnode("b0")}, {RDF.bnode("b0"), EX.baz(), 42} ]) end test "blank node property list on subject position" do assert Turtle.Decoder.decode!(""" [ 42 ] false . """) == Graph.new([ {RDF.bnode("b0"), EX.baz(), 42}, {RDF.bnode("b0"), EX.bar(), false} ]) end test "a single blank node property list" do assert Turtle.Decoder.decode!("[ 42 ] .") == Graph.new([{RDF.bnode("b0"), EX.foo(), 42}]) end test "nested blank node property list" do assert Turtle.Decoder.decode!(""" [ [ ] ; ]. """) == Graph.new([ {RDF.bnode("b0"), EX.p1(), RDF.bnode("b1")}, {RDF.bnode("b1"), EX.p2(), EX.o2()}, {RDF.bnode("b0"), EX.p(), EX.o()} ]) end test "blank node via []" do assert Turtle.Decoder.decode!(""" [] "Aaron Swartz" . """) == Graph.new({RDF.bnode("b0"), ~I, "Aaron Swartz"}) assert Turtle.Decoder.decode!(""" [] . """) == Graph.new({EX.Foo, EX.bar(), RDF.bnode("b0")}) assert Turtle.Decoder.decode!(""" [ ] . """) == Graph.new({EX.Foo, EX.bar(), RDF.bnode("b0")}) end end test "blank node" do assert Turtle.Decoder.decode!(""" _:foo . """) == Graph.new({RDF.bnode("foo"), EX.p(), EX.O}) assert Turtle.Decoder.decode!(""" _:1 . """) == Graph.new({EX.S, EX.p(), RDF.bnode("1")}) assert Turtle.Decoder.decode!(""" _:foo _:bar . """) == Graph.new({RDF.bnode("foo"), EX.p(), RDF.bnode("bar")}) end describe "quoted literals" do test "an untyped string literal" do assert Turtle.Decoder.decode!(""" "Peter Parker" . """) == Graph.new({EX.spiderman(), P.realname(), RDF.literal("Peter Parker")}) end test "an untyped long quoted string literal" do assert Turtle.Decoder.decode!(""" '''Peter Parker''' . """) == Graph.new({EX.spiderman(), P.realname(), RDF.literal("Peter Parker")}) end test "a typed literal" do assert Turtle.Decoder.decode!(""" "42"^^ . """) == Graph.new({EX.spiderman(), EX.p(), RDF.literal(42)}) end test "a typed literal with type as a prefixed name" do assert Turtle.Decoder.decode!(""" PREFIX xsd: "42"^^xsd:integer . """) == Graph.new({EX.spiderman(), EX.p(), RDF.literal(42)}, prefixes: %{xsd: NS.XSD}) end test "a language tagged literal" do assert Turtle.Decoder.decode!(""" "foo"@en . """) == Graph.new({EX.S, EX.p(), RDF.literal("foo", language: "en")}) end test "a '@prefix' or '@base' language tagged literal" do assert Turtle.Decoder.decode!(""" "foo"@prefix . """) == Graph.new({EX.S, EX.p(), RDF.literal("foo", language: "prefix")}) assert Turtle.Decoder.decode!(""" "foo"@base . """) == Graph.new({EX.S, EX.p(), RDF.literal("foo", language: "base")}) end end describe "shorthand literals" do test "boolean" do assert Turtle.Decoder.decode!(""" true . """) == Graph.new({EX.Foo, EX.bar(), XSD.true()}) assert Turtle.Decoder.decode!(""" false . """) == Graph.new({EX.Foo, EX.bar(), XSD.false()}) end test "integer" do assert Turtle.Decoder.decode!(""" 42 . """) == Graph.new({EX.Foo, EX.bar(), XSD.integer(42)}) end test "decimal" do assert Turtle.Decoder.decode!(""" 3.14 . """) == Graph.new({EX.Foo, EX.bar(), XSD.decimal("3.14")}) end test "double" do assert Turtle.Decoder.decode!(""" 1.2e3 . """) == Graph.new({EX.Foo, EX.bar(), XSD.double("1.2e3")}) end end describe "prefixed names" do test "non-empty prefixed names" do prefixes = RDF.PrefixMap.new(ex: ~I) assert Turtle.Decoder.decode!(""" @prefix ex: . ex:Aaron ex:Person . """) == Graph.new({EX.Aaron, RDF.type(), EX.Person}, prefixes: prefixes) assert Turtle.Decoder.decode!(""" @prefix ex: . ex:Aaron ex:Person . """) == Graph.new({EX.Aaron, RDF.type(), EX.Person}, prefixes: prefixes) assert Turtle.Decoder.decode!(""" PREFIX ex: ex:Aaron ex:Person . """) == Graph.new({EX.Aaron, RDF.type(), EX.Person}, prefixes: prefixes) assert Turtle.Decoder.decode!(""" prefix ex: ex:Aaron ex:Person . """) == Graph.new({EX.Aaron, RDF.type(), EX.Person}, prefixes: prefixes) end test "empty prefixed name" do prefixes = RDF.PrefixMap.new("": ~I) assert Turtle.Decoder.decode!(""" @prefix : . :Aaron :Person . """) == Graph.new({EX.Aaron, RDF.type(), EX.Person}, prefixes: prefixes) assert Turtle.Decoder.decode!(""" PREFIX : :Aaron :Person . """) == Graph.new({EX.Aaron, RDF.type(), EX.Person}, prefixes: prefixes) end end describe "collections" do test "non-empty collection" do assert Turtle.Decoder.decode!(""" @prefix : . :subject :predicate ( :a :b :c ) . """) == Graph.new( [ {EX.subject(), EX.predicate(), RDF.bnode("b0")}, {RDF.bnode("b0"), RDF.first(), EX.a()}, {RDF.bnode("b0"), RDF.rest(), RDF.bnode("b1")}, {RDF.bnode("b1"), RDF.first(), EX.b()}, {RDF.bnode("b1"), RDF.rest(), RDF.bnode("b2")}, {RDF.bnode("b2"), RDF.first(), EX.c()}, {RDF.bnode("b2"), RDF.rest(), RDF.nil()} ], prefixes: %{"": ~I} ) end test "empty collection" do assert Turtle.Decoder.decode!(""" @prefix : . :subject :predicate () . """) == Graph.new({EX.subject(), EX.predicate(), RDF.nil()}, prefixes: %{"": ~I} ) end test "nested collection" do assert Turtle.Decoder.decode!(""" @prefix : . :subject :predicate ( :a (:b :c) ) . """) == Graph.new( [ {EX.subject(), EX.predicate(), RDF.bnode("b0")}, {RDF.bnode("b0"), RDF.first(), EX.a()}, {RDF.bnode("b0"), RDF.rest(), RDF.bnode("b3")}, {RDF.bnode("b3"), RDF.first(), RDF.bnode("b1")}, {RDF.bnode("b3"), RDF.rest(), RDF.nil()}, {RDF.bnode("b1"), RDF.first(), EX.b()}, {RDF.bnode("b1"), RDF.rest(), RDF.bnode("b2")}, {RDF.bnode("b2"), RDF.first(), EX.c()}, {RDF.bnode("b2"), RDF.rest(), RDF.nil()} ], prefixes: %{"": ~I} ) end end describe "relative IRIs" do test "without explicit in-doc base and no document_base option option given" do assert_raise RuntimeError, fn -> Turtle.Decoder.decode!( "<#Aaron> <#Person> ." ) end end test "without explicit in-doc base, but document_base option given" do assert Turtle.Decoder.decode!( """ <#Aaron> <#Person> . """, base: "http://example.org/" ) == Graph.new({EX.Aaron, RDF.type(), EX.Person}, base_iri: ~I) end test "with @base given" do assert Turtle.Decoder.decode!(""" @base . <#Aaron> <#Person> . """) == Graph.new({EX.Aaron, RDF.type(), EX.Person}, base_iri: ~I) assert Turtle.Decoder.decode!(""" @base . <#Aaron> <#Person> . """) == Graph.new({EX.Aaron, RDF.type(), EX.Person}, base_iri: ~I) end test "with BASE given" do assert Turtle.Decoder.decode!(""" BASE <#Aaron> <#Person> . """) == Graph.new({EX.Aaron, RDF.type(), EX.Person}, base_iri: ~I) assert Turtle.Decoder.decode!(""" base <#Aaron> <#Person> . """) == Graph.new({EX.Aaron, RDF.type(), EX.Person}, base_iri: ~I) end test "when a given base is itself relative" do assert_raise RuntimeError, fn -> Turtle.Decoder.decode!(""" @base . <#Aaron> <#Person> . """) end assert_raise RuntimeError, fn -> Turtle.Decoder.decode!( "<#Aaron> <#Person> .", base: "foo" ) end end end end