require File.join(File.dirname(__FILE__), 'spec_helper')
include RdfContext

describe "N3 parser" do
  before(:each) { @parser = N3Parser.new(:strict => true) }
  
  describe "parse simple ntriples" do
    it "should parse simple triple" do
      n3_string = "<http://example.org/> <http://xmlns.com/foaf/0.1/name> \"Tom Morris\" . "
      @parser.parse(n3_string)
      @parser.graph[0].subject.to_s.should == "http://example.org/"
      @parser.graph[0].predicate.to_s.should == "http://xmlns.com/foaf/0.1/name"
      @parser.graph[0].object.to_s.should == "Tom Morris"
      @parser.graph.size.should == 1
    end
    
    it "should parse simple triple from class method" do
      n3_string = "<http://example.org/> <http://xmlns.com/foaf/0.1/name> \"Tom Morris\" . "
      graph = N3Parser.new(:strict => true).parse(n3_string)
      graph[0].subject.to_s.should == "http://example.org/"
      graph[0].predicate.to_s.should == "http://xmlns.com/foaf/0.1/name"
      graph[0].object.to_s.should == "Tom Morris"
      graph.size.should == 1
    end

    # NTriple tests from http://www.w3.org/2000/10/rdf-tests/rdfcore/ntriples/test.nt
    describe "recognize blank lines" do
      {
        "comment"                   => "# comment lines",
        "comment after whitespace"  => "            # comment after whitespace",
        "empty line"                => "",
        "line with spaces"          => "      "
      }.each_pair do |name, statement|
        specify "test #{name}" do
          @parser.parse(statement)
          @parser.graph.size.should == 0
        end
      end
    end

    it "should create named subject bnode" do
      @parser.parse("_:anon <http://example.org/property> <http://example.org/resource2> .")
      @parser.graph.should_not be_nil
      @parser.graph.size.should == 1
      triple = @parser.graph[0]
      triple.subject.should be_a(BNode)
      triple.subject.identifier.should =~ /anon/
      triple.predicate.should == "http://example.org/property"
      triple.object.should == "http://example.org/resource2"
    end

    it "should create named object bnode" do
      @parser.parse("<http://example.org/resource2> <http://example.org/property> _:anon .")
      @parser.graph.should_not be_nil
      @parser.graph.size.should == 1
      triple = @parser.graph[0]
      triple.subject.should == "http://example.org/resource2"
      triple.predicate.should == "http://example.org/property"
      triple.object.should be_a(BNode)
      triple.object.identifier.should =~ /anon/
    end

    {
      "three uris"  => "<http://example.org/resource1> <http://example.org/property> <http://example.org/resource2> .",
      "spaces and tabs throughout" => " 	 <http://example.org/resource3> 	 <http://example.org/property>	 <http://example.org/resource2> 	.	 ",
      "line ending with CR NL" => "<http://example.org/resource4> <http://example.org/property> <http://example.org/resource2> .\r\n",
      "literal escapes (1)" => '<http://example.org/resource7> <http://example.org/property> "simple literal" .',
      "literal escapes (2)" => '<http://example.org/resource8> <http://example.org/property> "backslash:\\\\" .',
      "literal escapes (3)" => '<http://example.org/resource9> <http://example.org/property> "dquote:\"" .',
      "literal escapes (4)" => '<http://example.org/resource10> <http://example.org/property> "newline:\n" .',
      "literal escapes (5)" => '<http://example.org/resource11> <http://example.org/property> "return:\r" .',
      "literal escapes (6)" => '<http://example.org/resource12> <http://example.org/property> "tab:\t" .',
      "Space is optional before final . (1)" => ['<http://example.org/resource13> <http://example.org/property> <http://example.org/resource2>.', '<http://example.org/resource13> <http://example.org/property> <http://example.org/resource2> .'],
      "Space is optional before final . (2)" => ['<http://example.org/resource14> <http://example.org/property> "x".', '<http://example.org/resource14> <http://example.org/property> "x" .'],

      "XML Literals as Datatyped Literals (1)" => '<http://example.org/resource21> <http://example.org/property> ""^^<http://www.w3.org/2000/01/rdf-schema#XMLLiteral> .',
      "XML Literals as Datatyped Literals (2)" => '<http://example.org/resource22> <http://example.org/property> " "^^<http://www.w3.org/2000/01/rdf-schema#XMLLiteral> .',
      "XML Literals as Datatyped Literals (3)" => '<http://example.org/resource23> <http://example.org/property> "x"^^<http://www.w3.org/2000/01/rdf-schema#XMLLiteral> .',
      "XML Literals as Datatyped Literals (4)" => '<http://example.org/resource23> <http://example.org/property> "\""^^<http://www.w3.org/2000/01/rdf-schema#XMLLiteral> .',
      "XML Literals as Datatyped Literals (5)" => '<http://example.org/resource24> <http://example.org/property> "<a></a>"^^<http://www.w3.org/2000/01/rdf-schema#XMLLiteral> .',
      "XML Literals as Datatyped Literals (6)" => '<http://example.org/resource25> <http://example.org/property> "a <b></b>"^^<http://www.w3.org/2000/01/rdf-schema#XMLLiteral> .',
      "XML Literals as Datatyped Literals (7)" => '<http://example.org/resource26> <http://example.org/property> "a <b></b> c"^^<http://www.w3.org/2000/01/rdf-schema#XMLLiteral> .',
      "XML Literals as Datatyped Literals (8)" => '<http://example.org/resource26> <http://example.org/property> "a\n<b></b>\nc"^^<http://www.w3.org/2000/01/rdf-schema#XMLLiteral> .',
      "XML Literals as Datatyped Literals (9)" => '<http://example.org/resource27> <http://example.org/property> "chat"^^<http://www.w3.org/2000/01/rdf-schema#XMLLiteral> .',
      
      "Plain literals with languages (1)" => '<http://example.org/resource30> <http://example.org/property> "chat"@fr .',
      "Plain literals with languages (2)" => '<http://example.org/resource31> <http://example.org/property> "chat"@en .',
      
      "Typed Literals" => '<http://example.org/resource32> <http://example.org/property> "abc"^^<http://example.org/datatype1> .',
    }.each_pair do |name, statement|
      specify "test #{name}" do
        @parser.parse([statement].flatten.first)
        @parser.graph.should_not be_nil
        @parser.graph.size.should == 1
        #puts @parser.graph[0].to_ntriples
        @parser.graph[0].to_ntriples.should == [statement].flatten.last.gsub(/\s+/, " ").strip
      end
    end
  end
  
  describe "literal encodings" do
    {
      'Dürst' => '_:a <http://example.org/named> "D\u00FCrst" .',
      'simple literal' => '<http://example.org/resource7> <http://example.org/property> "simple literal" .',
      'backslash:\\' => '<http://example.org/resource8> <http://example.org/property> "backslash:\\\\" .',
      'dquote:"' => '<http://example.org/resource9> <http://example.org/property> "dquote:\"" .',
      "newline:\n" => '<http://example.org/resource10> <http://example.org/property> "newline:\n" .',
      "return\r" => '<http://example.org/resource11> <http://example.org/property> "return\r" .',
      "tab:\t" => '<http://example.org/resource12> <http://example.org/property> "tab:\t" .',
      "é" => '<http://example.org/resource16> <http://example.org/property> "\u00E9" .',
      "€" => '<http://example.org/resource17> <http://example.org/property> "\u20AC" .',
    }.each_pair do |contents, triple|
      specify "test #{contents}" do
        @parser.parse(triple)
        @parser.graph.should_not be_nil
        @parser.graph.size.should == 1
        @parser.graph[0].object.contents.should == contents
      end
    end
  end
  
  # n3p tests taken from http://inamidst.com/n3p/test/
  describe "parsing n3p test" do
   dir_name = File.join(File.dirname(__FILE__), '..', 'test', 'n3_tests', 'n3p', '*.n3')
    Dir.glob(dir_name).each do |n3|    
      it n3 do
        test_file(n3)
      end
    end
  end
  
  describe "parsing real data tests" do
    dirs = [ 'misc', 'lcsh' ]
    dirs.each do |dir|
      dir_name = File.join(File.dirname(__FILE__), '..', 'test', 'n3_tests', dir, '*.n3')
      Dir.glob(dir_name).each do |n3|
        it "#{dir} #{n3}" do
          test_file(n3)
        end
      end
    end
  end
  
  it "should throw an exception when presented with a BNode as a predicate" do
    n3doc = "_:a _:b _:c ."
    lambda { @parser.parse(n3doc) }.should raise_error(RdfContext::Triple::InvalidPredicate)
  end

  it "should create BNodes" do
    n3doc = "_:a a _:c ."
    @parser.parse(n3doc)
    @parser.graph[0].subject.class.should == RdfContext::BNode
    @parser.graph[0].object.class.should == RdfContext::BNode
  end
  
  it "should create URIRefs" do
    n3doc = "<http://example.org/joe> <http://xmlns.com/foaf/0.1/knows> <http://example.org/jane> ."
    @parser.parse(n3doc)
    @parser.graph[0].subject.class.should == RdfContext::URIRef
    @parser.graph[0].object.class.should == RdfContext::URIRef
  end
  
  it "should create literals" do
    n3doc = "<http://example.org/joe> <http://xmlns.com/foaf/0.1/name> \"Joe\"."
    @parser.parse(n3doc)
    @parser.graph[0].object.class.should == RdfContext::Literal
  end
  
  it "should create typed literals" do
    n3doc = "<http://example.org/joe> <http://xmlns.com/foaf/0.1/name> \"Joe\"^^<http://www.w3.org/2001/XMLSchema#string> ."
    @parser.parse(n3doc)
    @parser.graph[0].object.class.should == RdfContext::Literal
  end
  
  it "should map <#> to document uri" do
    n3doc = "@prefix : <#> ."
    @parser.parse(n3doc, "http://the.document.itself")
    @parser.graph.nsbinding.should == {"__local__", Namespace.new("http://the.document.itself", "__local__")}
  end

  it "should parse testcase" do
    sampledoc = <<-EOF;
<http://www.w3.org/2000/10/rdf-tests/rdfcore/xmlbase/Manifest.rdf#test001> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/2000/10/rdf-tests/rdfcore/testSchema#PositiveParserTest> .
<http://www.w3.org/2000/10/rdf-tests/rdfcore/xmlbase/Manifest.rdf#test001> <http://www.w3.org/2000/10/rdf-tests/rdfcore/testSchema#approval> <http://lists.w3.org/Archives/Public/w3c-rdfcore-wg/2002Mar/0235.html> .
<http://www.w3.org/2000/10/rdf-tests/rdfcore/xmlbase/Manifest.rdf#test001> <http://www.w3.org/2000/10/rdf-tests/rdfcore/testSchema#inputDocument> <http://www.w3.org/2000/10/rdf-tests/rdfcore/xmlbase/test001.rdf> .
<http://www.w3.org/2000/10/rdf-tests/rdfcore/xmlbase/Manifest.rdf#test001> <http://www.w3.org/2000/10/rdf-tests/rdfcore/testSchema#issue> <http://www.w3.org/2000/03/rdf-tracking/#rdfms-xml-base> .
<http://www.w3.org/2000/10/rdf-tests/rdfcore/xmlbase/Manifest.rdf#test001> <http://www.w3.org/2000/10/rdf-tests/rdfcore/testSchema#outputDocument> <http://www.w3.org/2000/10/rdf-tests/rdfcore/xmlbase/test001.nt> .
<http://www.w3.org/2000/10/rdf-tests/rdfcore/xmlbase/Manifest.rdf#test001> <http://www.w3.org/2000/10/rdf-tests/rdfcore/testSchema#status> "APPROVED" .
<http://www.w3.org/2000/10/rdf-tests/rdfcore/xmlbase/test001.nt> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/2000/10/rdf-tests/rdfcore/testSchema#NT-Document> .
<http://www.w3.org/2000/10/rdf-tests/rdfcore/xmlbase/test001.rdf> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/2000/10/rdf-tests/rdfcore/testSchema#RDF-XML-Document> .
EOF
    @parser.parse(sampledoc, "http://www.w3.org/2000/10/rdf-tests/rdfcore/amp-in-url/Manifest.rdf")
    ntriples = @parser.graph.to_ntriples
    ntriples = sort_ntriples(ntriples)

    nt_string = sort_ntriples(sampledoc)
    ntriples.should == nt_string    
  end
  
  def test_file(filepath)
    anon = "a"
    anon_ctx = {}
    n3_string = File.read(filepath)
    @parser.parse(n3_string, "file:#{filepath}")
    ntriples = @parser.graph.to_ntriples
    ntriples.gsub!(/_:nbn\d+[a-z]+N/, "_:")  # Normalize named BNodes
    ntriples.gsub!(/_:bn\d+[a-z]+/) do |bn|
      # Normalize anon BNodes
      if anon_ctx[bn]
        anon_ctx[bn]
      else
        anon_ctx[bn] = anon
        anon = anon.succ
      end
      "_:#{anon_ctx[bn]}"
    end
    ntriples = sort_ntriples(ntriples)

    nt_string = File.read(filepath.sub('.n3', '.nt'))
    nt_string = sort_ntriples(nt_string)

    ntriples.should == nt_string    
  end
  
  def sort_ntriples(string)
    string.split("\n").sort.join("\n")
  end

end