require "test/unit/testcase"

require 'rexml/document'
require 'rexml/entity'
require 'rexml/source'

module REXMLTests
  class EntityTester < Test::Unit::TestCase
    def test_parse_general_decl
      simple = "<!ENTITY foo 'bar'>"
      simple =~ /#{REXML::Entity::GEDECL}/
      assert $&
      assert_equal simple, $&

      REXML::Entity::ENTITYDECL =~ simple
      assert REXML::Entity::matches?(simple)
      match = REXML::Entity::ENTITYDECL.match(simple)
      assert_equal 'foo', match[1]
      assert_equal "'bar'", match[2]

      simple = '<!ENTITY Pub-Status
      "This is a pre-release of the specification.">'
      assert REXML::Entity::matches?(simple)
      match = REXML::Entity::ENTITYDECL.match(simple)
      assert_equal 'Pub-Status', match[1]
      assert_equal '"This is a pre-release of the specification."', match[2]

      txt = '"This is a
      pre-release of <the> specification."'
      simple = "<!ENTITY     Pub-Status
      #{txt}>"
      assert REXML::Entity::matches?(simple)
      match = REXML::Entity::ENTITYDECL.match(simple)
      assert_equal 'Pub-Status', match[1]
      assert_equal txt, match[2]
    end

    def test_parse_external_decl
      zero = '<!ENTITY open-hatch SYSTEM "http://www.textuality.com/boilerplate/OpenHatch.xml" >'
      one = '<!ENTITY open-hatch
                SYSTEM "http://www.textuality.com/boilerplate/OpenHatch.xml">'
      two = '<!ENTITY open-hatch
                PUBLIC "-//Textuality//TEXT Standard open-hatch boilerplate//EN"
                "http://www.textuality.com/boilerplate/OpenHatch.xml">'
      three = '<!ENTITY hatch-pic
                SYSTEM "../grafix/OpenHatch.gif"
                NDATA gif >'
      assert REXML::Entity::matches?(zero)
      assert REXML::Entity::matches?(one)
      assert REXML::Entity::matches?(two)
      assert REXML::Entity::matches?(three)
    end

    def test_parse_entity
      one = %q{<!ENTITY % YN '"Yes"'>}
      two = %q{<!ENTITY WhatHeSaid "He said %YN;">}
      assert REXML::Entity::matches?(one)
      assert REXML::Entity::matches?(two)
    end

    def test_constructor
      one = [ %q{<!ENTITY % YN '"Yes"'>},
        %q{<!ENTITY % YN2 "Yes">},
        %q{<!ENTITY WhatHeSaid "He said %YN;">},
        '<!ENTITY open-hatch
                SYSTEM "http://www.textuality.com/boilerplate/OpenHatch.xml">',
        '<!ENTITY open-hatch2
                PUBLIC "-//Textuality//TEXT Standard open-hatch boilerplate//EN"
                "http://www.textuality.com/boilerplate/OpenHatch.xml">',
        '<!ENTITY hatch-pic
                SYSTEM "../grafix/OpenHatch.gif"
                NDATA gif>' ]
      source = %q{<!DOCTYPE foo [
        <!ENTITY % YN '"Yes"'>
        <!ENTITY % YN2 "Yes">
        <!ENTITY WhatHeSaid "He said %YN;">
        <!ENTITY open-hatch
                SYSTEM "http://www.textuality.com/boilerplate/OpenHatch.xml">
        <!ENTITY open-hatch2
                PUBLIC "-//Textuality//TEXT Standard open-hatch boilerplate//EN"
                "http://www.textuality.com/boilerplate/OpenHatch.xml">
        <!ENTITY hatch-pic
                SYSTEM "../grafix/OpenHatch.gif"
                NDATA gif>
      ]>}

      d = REXML::Document.new( source )
      dt = d.doctype
      c = 0
      dt.each do |child|
        if child.kind_of? REXML::Entity
          str = one[c].tr("\r\n\t", '   ').squeeze(" ")
          assert_equal str, child.to_s
          c+=1
        end
      end
    end

    def test_replace_entities
      source = "<!DOCTYPE blah [\n<!ENTITY foo \"bar\">\n]><a>&foo;</a>"
      doc = REXML::Document.new(source)
      assert_equal 'bar', doc.root.text
      out = ''
      doc.write out
      assert_equal source, out
    end

    def test_entity_string_limit
      template = '<!DOCTYPE bomb [ <!ENTITY a "^" > ]> <bomb>$</bomb>'
      len      = 5120 # 5k per entity
      template.sub!(/\^/, "B" * len)

      # 10k is OK
      entities = '&a;' * 2 # 5k entity * 2 = 10k
      xmldoc = REXML::Document.new(template.sub(/\$/, entities))
      assert_equal(len * 2, xmldoc.root.text.bytesize)

      # above 10k explodes
      entities = '&a;' * 3 # 5k entity * 2 = 15k
      xmldoc = REXML::Document.new(template.sub(/\$/, entities))
      assert_raises(RuntimeError) do
        xmldoc.root.text
      end
    end

    def test_entity_string_limit_for_parameter_entity
      template = '<!DOCTYPE bomb [ <!ENTITY % a "^" > <!ENTITY bomb "$" > ]><root/>'
      len      = 5120 # 5k per entity
      template.sub!(/\^/, "B" * len)

      # 10k is OK
      entities = '%a;' * 2 # 5k entity * 2 = 10k
      REXML::Document.new(template.sub(/\$/, entities))

      # above 10k explodes
      entities = '%a;' * 3 # 5k entity * 2 = 15k
      assert_raises(REXML::ParseException) do
        REXML::Document.new(template.sub(/\$/, entities))
      end
    end

    def test_raw
      source = '<!DOCTYPE foo [
<!ENTITY ent "replace">
]><a>replace &ent;</a>'
      doc = REXML::Document.new( source, {:raw=>:all})
      assert_equal('replace &ent;', doc.root.get_text.to_s)
      assert_equal(source, doc.to_s)
    end

    def test_lazy_evaluation
      source = '<!DOCTYPE foo [
<!ENTITY ent "replace">
]><a>replace &ent;</a>'
      doc = REXML::Document.new( source )
      assert_equal(source, doc.to_s)
      assert_equal("replace replace", doc.root.text)
      assert_equal(source, doc.to_s)
    end

    # Contributed (not only test, but bug fix!!) by Kouhei Sutou
    def test_entity_replacement
      source = %q{<!DOCTYPE foo [
      <!ENTITY % YN '"Yes"'>
      <!ENTITY WhatHeSaid "He said %YN;">]>
      <a>&WhatHeSaid;</a>}

      d = REXML::Document.new( source )
      dt = d.doctype
      assert_equal( '"Yes"', dt.entities[ "YN" ].value )
      assert_equal( 'He said "Yes"', dt.entities[ "WhatHeSaid" ].value )
      assert_equal( 'He said "Yes"', d.elements[1].text )
    end

    # More unit tests from Kouhei.  I looove users who give me unit tests.
    def test_entity_insertions
      assert_equal("&amp;", REXML::Text.new("&amp;", false, nil, true).to_s)
      #assert_equal("&", REXML::Text.new("&amp;", false, false).to_s)
    end

    def test_single_pass_unnormalization # ticket 123
      assert_equal '&amp;&', REXML::Text::unnormalize('&#38;amp;&amp;')
    end

    def test_entity_filter
      document = REXML::Document.new(<<-XML)
<!DOCTYPE root [
<!ENTITY copy "(c)">
<!ENTITY release-year "2013">
]>
<root/>
XML
      respect_whitespace = false
      parent = document.root
      raw = false
      entity_filter = ["copy"]
      assert_equal("(c) &release-year;",
                   REXML::Text.new("(c) 2013",
                                   respect_whitespace,
                                   parent,
                                   raw,
                                   entity_filter).to_s)
    end
  end
end
