1# frozen_string_literal: false
2require_relative "rexml_test_utils"
3require 'rexml/sax2listener'
4require 'rexml/parsers/sax2parser'
5require 'rexml/document'
6
7module REXMLTests
8  class SAX2Tester < Test::Unit::TestCase
9    include REXMLTestUtils
10    include REXML
11    def test_characters
12      d = Document.new( "<A>@blah@</A>" )
13      txt = d.root.text
14      p = Parsers::SAX2Parser.new "<A>@blah@</A>"
15      p.listen(:characters) {|x| assert_equal txt, x}
16      p.listen(:characters, ["A"]) {|x| assert_equal txt,x}
17      p.parse
18    end
19
20    def test_entity_replacement
21      source = '<!DOCTYPE foo [
22      <!ENTITY la "1234">
23      <!ENTITY lala "--&la;--">
24      <!ENTITY lalal "&la;&la;">
25      ]><a><la>&la;</la><lala>&lala;</lala></a>'
26      sax = Parsers::SAX2Parser.new( source )
27      results = []
28      sax.listen(:characters) {|x| results << x }
29      sax.parse
30      assert_equal 2, results.size
31      assert_equal '1234', results[0]
32      assert_equal '--1234--', results[1]
33    end
34
35    def test_sax2
36      File.open(fixture_path("documentation.xml")) do |f|
37        parser = Parsers::SAX2Parser.new( f )
38        # Listen to all events on the following elements
39        count = 0
40        blok = proc { |uri,localname,qname,attributes|
41          assert %w{ bugs todo }.include?(localname),
42          "Mismatched name; we got '#{qname}'\nArgs were:\n\tURI: #{uri}\n\tLOCALNAME: #{localname}\n\tQNAME: #{qname}\n\tATTRIBUTES: #{attributes.inspect}\n\tSELF=#{blok}"
43          count += 1
44        }
45
46        start_document = 0
47        end_document = 0
48        parser.listen( :start_document ) { start_document += 1 }
49        parser.listen( :end_document ) { end_document += 1 }
50        parser.listen( :start_element, %w{ changelog bugs todo }, &blok )
51        # Listen to all events on the following elements.  Synonymous with
52        # listen( :start_element, %w{ ... } )
53        parser.listen( %w{ changelog bugs todo }, &blok )
54        # Listen for all start element events
55        parser.listen( :start_element ) { |uri,localname,qname,attributes|
56        }
57        listener = MySAX2Listener.new
58        # Listen for all events
59        parser.listen( listener )
60        # Listen for all events on the given elements.  Does not include children
61        # events.  Regular expressions work as well!
62        parser.listen( %w{ /change/ bugs todo }, listener )
63        # Test the deafening method
64        blok = proc  { |uri,localname,qname,attributes|
65          assert_fail "This listener should have been deafened!"
66        }
67        parser.listen( %w{ changelog }, &blok )
68        parser.deafen( &blok )
69
70        tc = 0
71        parser.listen( :characters, %w{version} ) {|text|
72          assert(text=~/@ANT_VERSION@/, "version was '#{text}'")
73          tc += 1
74        }
75
76        begin
77          parser.parse
78        rescue => exception
79          if exception.kind_of? Test::Unit::AssertionFailedError
80            raise exception
81          end
82          puts $!
83          puts exception.backtrace
84        end
85        assert_equal 2, count
86        assert_equal 1, tc
87        assert_equal 1, start_document
88        assert_equal 1, end_document
89      end
90    end
91
92    # used by test_simple_doctype_listener
93    # submitted by Jeff Barczewski
94    class SimpleDoctypeListener
95      include REXML::SAX2Listener
96      attr_reader :name, :pub_sys, :long_name, :uri
97
98      def initialize
99        @name = @pub_sys = @long_name = @uri = nil
100      end
101
102      def doctype(name, pub_sys, long_name, uri)
103        @name = name
104        @pub_sys = pub_sys
105        @long_name = long_name
106        @uri = uri
107      end
108    end
109
110    # test simple non-entity doctype in sax listener
111    # submitted by Jeff Barczewski
112    def test_simple_doctype_listener
113      xml = <<-END
114        <?xml version="1.0"?>
115        <!DOCTYPE greeting PUBLIC "Hello Greeting DTD" "http://foo/hello.dtd">
116        <greeting>Hello, world!</greeting>
117      END
118      parser = Parsers::SAX2Parser.new(xml)
119      dtl = SimpleDoctypeListener.new
120      parser.listen(dtl)
121      tname = nil
122      tpub_sys = nil
123      tlong_name = nil
124      turi = nil
125      parser.listen(:doctype) do |name, pub_sys, long_name, uri|
126        tname = name
127        tpub_sys = pub_sys
128        tlong_name = long_name
129        turi = uri
130      end
131      parser.parse
132      assert_equal 'greeting', tname, 'simple doctype block listener failed - incorrect name'
133      assert_equal 'PUBLIC', tpub_sys, 'simple doctype block listener failed - incorrect pub_sys'
134      assert_equal 'Hello Greeting DTD', tlong_name, 'simple doctype block listener failed - incorrect long_name'
135      assert_equal 'http://foo/hello.dtd', turi, 'simple doctype block listener failed - incorrect uri'
136      assert_equal 'greeting', dtl.name, 'simple doctype listener failed - incorrect name'
137      assert_equal 'PUBLIC', dtl.pub_sys, 'simple doctype listener failed - incorrect pub_sys'
138      assert_equal 'Hello Greeting DTD', dtl.long_name, 'simple doctype listener failed - incorrect long_name'
139      assert_equal 'http://foo/hello.dtd', dtl.uri, 'simple doctype listener failed - incorrect uri'
140    end
141
142    # test doctype with missing name, should throw ParseException
143    # submitted by Jeff Barczewseki
144    def test_doctype_with_mising_name_throws_exception
145      xml = <<-END
146        <?xml version="1.0"?>
147        <!DOCTYPE >
148        <greeting>Hello, world!</greeting>
149      END
150      parser = Parsers::SAX2Parser.new(xml)
151      assert_raise(REXML::ParseException, 'doctype missing name did not throw ParseException') do
152        parser.parse
153      end
154    end
155
156
157    class KouListener
158      include REXML::SAX2Listener
159      attr_accessor :sdoc, :edoc
160      attr_reader :selem, :decl, :pi
161      def initialize
162        @sdoc = @edoc = @selem = false
163        @decl = 0
164        @pi = 0
165      end
166      def start_document
167        @sdoc = true
168      end
169      def end_document
170        @edoc = true
171      end
172      def xmldecl( *arg )
173        @decl += 1
174      end
175      def processing_instruction( *arg )
176        @pi += 1
177      end
178      def start_element( *arg )
179        @selem = true
180      end
181    end
182
183    # Submitted by Kou
184    def test_begin_end_document
185      parser = Parsers::SAX2Parser.new("<a/>")
186
187      kl = KouListener.new
188      parser.listen(kl)
189      sd = false
190      ed = false
191      parser.listen(:start_document) { sd = true }
192      parser.listen(:end_document) { ed = true }
193
194      parser.parse
195      assert( sd, ':start_document block failed' )
196      assert( ed, ':end_document block failed' )
197      assert( kl.sdoc, ':start_document listener failed' )
198      assert( kl.edoc, ':end_document listener failed' )
199    end
200
201    # Submitted by Kou
202    def test_listen_before_start
203      # FIXME: the following comment should be a test for validity. (The xml declaration
204      # is invalid).
205      #parser =  Parsers::SAX2Parser.new( "<?xml ?><?pi?><a><?pi?></a>")
206      parser =  Parsers::SAX2Parser.new( "<?xml version='1.0'?><?pi?><a><?pi?></a>")
207      k1 = KouListener.new
208      parser.listen( k1 )
209      xmldecl = false
210      pi = 0
211      parser.listen( :xmldecl ) { xmldecl = true }
212      parser.listen( :processing_instruction ) { pi += 1 }
213
214      parser.parse
215
216      assert( xmldecl, ':xmldecl failed' )
217      assert_equal( 2, pi, ':processing_instruction failed' )
218      assert( k1.decl, 'Listener for xmldecl failed' )
219      assert_equal( 2, k1.pi, 'Listener for processing instruction failed' )
220    end
221
222
223    def test_socket
224      require 'socket'
225
226      TCPServer.open('127.0.0.1', 0) do |server|
227        TCPSocket.open('127.0.0.1', server.addr[1]) do |socket|
228          ok = false
229          session = server.accept
230          begin
231            session << '<foo>'
232            parser = REXML::Parsers::SAX2Parser.new(socket)
233            Fiber.new do
234              parser.listen(:start_element) do
235                ok = true
236                Fiber.yield
237              end
238              parser.parse
239            end.resume
240            assert(ok)
241          ensure
242            session.close
243          end
244        end
245      end
246    end
247
248    def test_char_ref_sax2()
249      parser = REXML::Parsers::SAX2Parser.new('<ABC>&#252;</ABC>')
250      result = nil
251      parser.listen(:characters) {|text| result = text.unpack('U*')}
252      parser.parse()
253      assert_equal(1, result.size)
254      assert_equal(252, result[0])
255    end
256
257
258    def test_char_ref_dom()
259      doc = REXML::Document.new('<ABC>&#252;</ABC>')
260      result = doc.root.text.unpack('U*')
261      assert_equal(1, result.size)
262      assert_equal(252, result[0])
263    end
264
265    class Ticket68
266      include REXML::SAX2Listener
267    end
268    def test_ticket_68
269      File.open(fixture_path('ticket_68.xml')) do |f|
270        parser = REXML::Parsers::SAX2Parser.new(f)
271        parser.listen( Ticket68.new )
272        begin
273          parser.parse
274        rescue
275          p parser.source.position
276          p parser.source.current_line
277          puts $!.backtrace.join("\n")
278          flunk $!.message
279        end
280      end
281    end
282  end
283
284  class MySAX2Listener
285    include REXML::SAX2Listener
286  end
287end
288