#!/usr/bin/env ruby
# Ubuntu does not accept arguments to ruby when called using env. To get warnings to show up the -w options is
# required. That can be set in the RUBYOPT environment variable.
# export RUBYOPT=-w

$VERBOSE = true

$: << File.join(File.dirname(__FILE__), '../../lib')
$: << File.join(File.dirname(__FILE__), '../../ext')
$: << File.join(File.dirname(__FILE__), '.')

require 'stringio'
require 'bigdecimal'
require 'test/unit'
require 'optparse'

require 'ox'

require 'helpers'
# require 'smart_test'

opts = OptionParser.new
opts.on('-h', '--help', 'Show this display') { puts opts; Process.exit!(0) }
opts.parse(ARGV)

$ox_sax_options = {
  encoding: nil,
  indent: 2,
  trace: 0,
  with_dtd: false,
  with_xml: false,
  with_instructions: false,
  circular: false,
  xsd_date: false,
  mode: :object,
  symbolize_keys: true,
  skip: :skip_return,
  smart: false,
  convert_special: true,
  effort: :strict,
  invalid_replace: '',
  strip_namespace: false
}

class SaxBaseTest < Test::Unit::TestCase
  include SaxTestHelpers

  def test_sax_io_pipe
    Ox.default_options = $ox_sax_options
    handler = AllSax.new
    input, w = IO.pipe
    w << %{<top/>}
    w.close
    Ox.sax_parse(handler, input)
    assert_equal([
                   [:start_element, :top],
                   [:end_element, :top]
                 ], handler.calls)
  end

  def test_sax_file
    Ox.default_options = $ox_sax_options
    handler = AllSax.new
    input = File.open(File.join(File.dirname(__FILE__), 'basic.xml'))
    Ox.sax_parse(handler, input)
    input.close
    assert_equal([
                   [:start_element, :top],
                   [:end_element, :top]
                 ], handler.calls)
  end

  def test_sax_file_line_col
    Ox.default_options = $ox_sax_options
    handler = LineColSax.new
    input = File.open(File.join(File.dirname(__FILE__), 'trilevel.xml'))
    Ox.sax_parse(handler, input)
    input.close
    assert_equal([
                   [:instruct, 'xml', 1, 1, 1],
                   [:attr, :version, '1.0', 15, 1, 15],
                   [:attrs_done, 15, 1, 15],
                   [:end_instruct, 'xml', 20, 1, 20],
                   [:start_element, :top, 23, 2, 1],
                   [:attrs_done, 23, 2, 1],
                   [:start_element, :child, 31, 3, 3],
                   [:attrs_done, 31, 3, 3],
                   [:start_element, :grandchild, 43, 4, 5],
                   [:attrs_done, 43, 4, 5],
                   [:end_element, :grandchild, 55, 4, 17],
                   [:end_element, :child, 59, 5, 3],
                   [:end_element, :top, 68, 6, 1]
                 ], handler.calls)
  end

  def test_sax_io_file
    Ox.default_options = $ox_sax_options
    handler = AllSax.new
    input = IO.open(IO.sysopen(File.join(File.dirname(__FILE__), 'basic.xml')))
    Ox.sax_parse(handler, input)
    input.close
    assert_equal([
                   [:start_element, :top],
                   [:end_element, :top]
                 ], handler.calls)
  end

  def test_sax_instruct_simple
    Ox.default_options = $ox_sax_options
    parse_compare(%{<?xml?>}, [
                    [:instruct, 'xml'],
                    [:end_instruct, 'xml']
                  ])
  end

  def test_sax_instruct_blank
    Ox.default_options = $ox_sax_options
    parse_compare(%{<?xml?>}, [], StartSax)
  end

  def test_sax_instruct_attrs
    Ox.default_options = $ox_sax_options
    parse_compare(%{<?xml version="1.0" encoding="UTF-8"?>},
                  [
                    [:instruct, 'xml'],
                    [:attr, :version, '1.0'],
                    [:attr, :encoding, 'UTF-8'],
                    [:end_instruct, 'xml']
                  ])
  end

  def test_sax_instruct_noattrs
    Ox.default_options = $ox_sax_options
    parse_compare(%{<?bad something?>},
                  [
                    [:instruct, 'bad'],
                    [:text, 'something'],
                    [:end_instruct, 'bad']
                  ])
  end

  def test_sax_instruct_loose
    Ox.default_options = $ox_sax_options
    parse_compare(%{<? xml
version = "1.0"
encoding = "UTF-8" ?>},
                  [
                    [:instruct, 'xml'],
                    [:attr, :version, '1.0'],
                    [:attr, :encoding, 'UTF-8'],
                    [:end_instruct, 'xml']
                  ])
  end

  def test_sax_instruct_pi
    Ox.default_options = $ox_sax_options
    parse_compare(%{<?pro cat="quick"?>
<top>
  <str>This is a <?attrs dog="big"?> string.</str>
  <?content dog is big?>
</top>
},
                  [
                    [:instruct, 'pro'],
                    [:attr, :cat, 'quick'],
                    [:end_instruct, 'pro'],
                    [:start_element, :top],
                    [:start_element, :str],
                    [:text, 'This is a '],
                    [:instruct, 'attrs'],
                    [:attr, :dog, 'big'],
                    [:end_instruct, 'attrs'],
                    [:text, ' string.'],
                    [:end_element, :str],
                    [:instruct, 'content'],
                    [:text, 'dog is big'],
                    [:end_instruct, 'content'],
                    [:end_element, :top]
                  ])
  end

  def test_sax_instruct_no_term
    Ox.default_options = $ox_sax_options
    parse_compare(%{<?xml?
<top/>},
                  [
                    [:instruct, 'xml'],
                    [:error, 'Not Terminated: instruction not terminated', 1, 6],
                    [:end_instruct, 'xml'],
                    [:start_element, :top],
                    [:end_element, :top]
                  ])
  end

  def test_sax_element_simple
    Ox.default_options = $ox_sax_options
    parse_compare(%{<top/>},
                  [
                    [:start_element, :top],
                    [:end_element, :top]
                  ])
  end

  def test_sax_element_attrs
    Ox.default_options = $ox_sax_options
    parse_compare(%{<top x="57" y='42' z=33/>},
                  [
                    [:start_element, :top],
                    [:attr, :x, '57'],
                    [:attr, :y, '42'],
                    [:error, 'Unexpected Character: attribute value not in quotes', 1, 22],
                    [:attr, :z, '33/'],
                    [:error, "Start End Mismatch: element 'top' not closed", 1, 25],
                    [:end_element, :top]
                  ])
  end

  def test_sax_element_start_end
    Ox.default_options = $ox_sax_options
    parse_compare(%{<top></top>},
                  [
                    [:start_element, :top],
                    [:text, ''],
                    [:end_element, :top]
                  ])
  end

  def test_sax_two_top
    Ox.default_options = $ox_sax_options
    parse_compare(%{<top/><top/>},
                  [
                    [:start_element, :top],
                    [:end_element, :top],
                    [:error, 'Out of Order: multiple top level elements', 1, 7],
                    [:start_element, :top],
                    [:end_element, :top]
                  ])
  end

  def test_sax_nested_elements
    Ox.default_options = $ox_sax_options
    parse_compare(%{<top>
  <child>
    <grandchild/>
  </child >
</top>
},
                  [
                    [:start_element, :top],
                    [:start_element, :child],
                    [:start_element, :grandchild],
                    [:end_element, :grandchild],
                    [:end_element, :child],
                    [:end_element, :top]
                  ])
  end

  def test_sax_elements_case
    Ox.default_options = $ox_sax_options
    Ox.default_options = { smart: true, skip: :skip_white }
    parse_compare(%{<top>
  <Child>
    <grandchild/>
  </child >
</TOP>
},
                  [
                    [:start_element, :top],
                    [:start_element, :Child],
                    [:start_element, :grandchild],
                    [:end_element, :grandchild],
                    [:end_element, :Child],
                    [:end_element, :top]
                  ], AllSax, { smart: true })
  end

  def test_sax_nested1
    Ox.default_options = $ox_sax_options
    parse_compare(%{<?xml version="1.0"?>
<top>
  <child>
    <grandchild/>
  </child>
</top>
},
                  [
                    [:instruct, 'xml'],
                    [:attr, :version, '1.0'],
                    [:end_instruct, 'xml'],
                    [:start_element, :top],
                    [:start_element, :child],
                    [:start_element, :grandchild],
                    [:end_element, :grandchild],
                    [:end_element, :child],
                    [:end_element, :top]
                  ])
  end

  def test_sax_line_col
    Ox.default_options = $ox_sax_options
    parse_compare(%{<?xml version="1.0"?>
<!DOCTYPE top PUBLIC "top.dtd">
<top attr="one">
  <!-- family -->
  <child>
    <grandchild/>
    <![CDATA[see data.]]>
  </child>Some text.
</top>
<second>
  <inside/>
</second>
},
                  [
                    [:instruct, 'xml', 1, 1, 1],
                    [:attr, :version, '1.0', 15, 1, 15],
                    [:attrs_done, 15, 1, 15],
                    [:end_instruct, 'xml', 20, 1, 20],
                    [:doctype, ' top PUBLIC "top.dtd"', 23, 2, 1],
                    [:start_element, :top, 55, 3, 1],
                    [:attr, :attr, 'one', 65, 3, 11],
                    [:attrs_done, 65, 3, 11],
                    [:comment, ' family ', 74, 4, 3],
                    [:start_element, :child, 92, 5, 3],
                    [:attrs_done, 92, 5, 3],
                    [:start_element, :grandchild, 104, 6, 5],
                    [:attrs_done, 104, 6, 5],
                    [:end_element, :grandchild, 116, 6, 17],
                    [:cdata, 'see data.', 122, 7, 5],
                    [:end_element, :child, 146, 8, 3],
                    [:text, "Some text.\n", 154, 8, 10],
                    [:end_element, :top, 165, 9, 1],
                    [:error, 'Out of Order: multiple top level elements', 10, 1],
                    [:start_element, :second, 172, 10, 1],
                    [:attrs_done, 172, 10, 1],
                    [:start_element, :inside, 183, 11, 3],
                    [:attrs_done, 183, 11, 3],
                    [:end_element, :inside, 191, 11, 11],
                    [:end_element, :second, 193, 12, 1]
                  ], LineColSax)
  end

  def test_sax_element_name_mismatch
    Ox.default_options = $ox_sax_options
    parse_compare(%{<?xml version="1.0"?>
<top>
  <child>
    <grandchild/>
  </parent>
</top>},
                  [
                    [:instruct, 'xml'],
                    [:attr, :version, '1.0'],
                    [:end_instruct, 'xml'],
                    [:start_element, :top],
                    [:start_element, :child],
                    [:start_element, :grandchild],
                    [:end_element, :grandchild],
                    [:error, "Start End Mismatch: element 'parent' closed but not opened", 5, 3],
                    [:start_element, :parent],
                    [:end_element, :parent],
                    [:error, "Start End Mismatch: element 'top' close does not match 'child' open", 6, 1],
                    [:end_element, :child],
                    [:end_element, :top]
                  ])
  end

  def test_sax_nested
    Ox.default_options = $ox_sax_options
    parse_compare(%{<?xml version="1.0"?>
<top>
  <child>
    <grandchild/>
  </child>
  <child>
    <grandchild/>
    <grandchild/>
  </child>
</top>
},
                  [
                    [:instruct, 'xml'],
                    [:attr, :version, '1.0'],
                    [:end_instruct, 'xml'],
                    [:start_element, :top],
                    [:start_element, :child],
                    [:start_element, :grandchild],
                    [:end_element, :grandchild],
                    [:end_element, :child],
                    [:start_element, :child],
                    [:start_element, :grandchild],
                    [:end_element, :grandchild],
                    [:start_element, :grandchild],
                    [:end_element, :grandchild],
                    [:end_element, :child],
                    [:end_element, :top]
                  ])
  end

  def test_sax_element_no_term
    Ox.default_options = $ox_sax_options
    parse_compare(%{
<top>
  <child/>
},
                  [
                    [:start_element, :top],
                    [:start_element, :child],
                    [:end_element, :child],
                    [:error, "Start End Mismatch: element 'top' not closed", 4, 0],
                    [:end_element, :top]
                  ])
  end

  def test_sax_text
    Ox.default_options = $ox_sax_options
    parse_compare(%{<top>This is some text.</top>},
                  [
                    [:start_element, :top],
                    [:text, 'This is some text.'],
                    [:end_element, :top]
                  ])
  end

  def test_sax_open_close_element
    Ox.default_options = $ox_sax_options
    parse_compare(%{<top><mid></mid></top>},
                  [
                    [:start_element, :top],
                    [:start_element, :mid],
                    [:text, ''],
                    [:end_element, :mid],
                    [:end_element, :top]
                  ])
  end

  def test_sax_last_element_closed_not_opened
    Ox.default_options = $ox_sax_options
    parse_compare(%{</top>},
                  [
                    [:error, "Start End Mismatch: element 'top' closed but not opened", 1, 1],
                    [:start_element, :top],
                    [:end_element, :top]
                  ])
  end

  def test_sax_last_unnamed_element_closed_not_opened
    Ox.default_options = $ox_sax_options
    parse_compare(%{</>},
                  [
                    [:error, "Start End Mismatch: element '' closed but not opened", 1, 1],
                    [:start_element, :''],
                    [:end_element, :'']
                  ])
  end

  def test_sax_empty_element
    Ox.default_options = $ox_sax_options
    parse_compare(%{  <top>  </top>},
                  [
                    [:start_element, :top],
                    [:text, ' '],
                    [:end_element, :top]
                  ], AllSax, { skip: :skip_white })
  end

  def test_sax_empty_element_name
    Ox.default_options = $ox_sax_options
    parse_compare(%{<?xml version="1.0"?>
<top>
  <>
  <abc x="1">zzz</abc>
</top>},
                  [
                    [:instruct, 'xml'],
                    [:attr, :version, '1.0'],
                    [:end_instruct, 'xml'],
                    [:start_element, :top],
                    [:error, 'Invalid Format: empty element', 3, 3],
                    [:start_element, :abc],
                    [:attr, :x, '1'],
                    [:text, 'zzz'],
                    [:end_element, :abc],
                    [:end_element, :top]
                  ])
  end

  def test_sax_empty_element_noskip
    Ox.default_options = $ox_sax_options
    parse_compare(%{  <top>  </top>},
                  [
                    [:text, '  '],
                    [:start_element, :top],
                    [:text, '  '],
                    [:end_element, :top]
                  ], AllSax, { skip: :skip_none })
  end

  def test_sax_special
    Ox.default_options = $ox_sax_options
    parse_compare(%{<top name="A&amp;Z">This is &lt;some&gt; &pi; text.</top>},
                  [
                    [:start_element, :top],
                    [:attr, :name, 'A&Z'],
                    [:text, "This is <some> \u03c0 text."],
                    [:end_element, :top]
                  ], AllSax, convert_special: true)
  end

  def test_sax_bad_special
    Ox.default_options = $ox_sax_options
    parse_compare(%{<top name="&example;">&example;</top>},
                  [
                    [:start_element, :top],
                    [:error, 'Invalid Format: Invalid special character sequence', 1, 11],
                    [:attr, :name, '&example;'],
                    [:error, 'Invalid Format: Invalid special character sequence', 1, 22],
                    [:text, '&example;'],
                    [:end_element, :top]
                  ], AllSax, convert_special: true)
  end

  def test_sax_ignore_special
    Ox.default_options = $ox_sax_options
    parse_compare(%{<top name="&example;">&example;</top>},
                  [
                    [:start_element, :top],
                    [:attr, :name, '&example;'],
                    [:text, '&example;'],
                    [:end_element, :top]
                  ], AllSax, convert_special: false)
  end

  def test_sax_not_special
    Ox.default_options = $ox_sax_options
    parse_compare(%{<top name="A&amp;Z">This is &lt;some&gt; text.</top>},
                  [
                    [:start_element, :top],
                    [:attr, :name, 'A&amp;Z'],
                    [:text, 'This is &lt;some&gt; text.'],
                    [:end_element, :top]
                  ], AllSax, convert_special: false)
  end

  def test_sax_special_default
    Ox.default_options = $ox_sax_options
    orig = Ox.default_options[:convert_special]
    Ox.default_options = { convert_special: true }
    parse_compare(%{<top name="A&amp;Z">This is &lt;some&gt; text.</top>},
                  [
                    [:start_element, :top],
                    [:attr, :name, 'A&Z'],
                    [:text, 'This is <some> text.'],
                    [:end_element, :top]
                  ], AllSax)
    Ox.default_options = { convert_special: orig }
  end

  def test_sax_not_special_default
    Ox.default_options = $ox_sax_options
    orig = Ox.default_options[:convert_special]
    Ox.default_options = { convert_special: false }
    parse_compare(%{<top name="A&amp;Z">This is &lt;some&gt; text.</top>},
                  [
                    [:start_element, :top],
                    [:attr, :name, 'A&amp;Z'],
                    [:text, 'This is &lt;some&gt; text.'],
                    [:end_element, :top]
                  ], AllSax)
    Ox.default_options = { convert_special: orig }
  end

  def test_sax_whitespace
    Ox.default_options = $ox_sax_options
    parse_compare(%{<top> This is some text.</top>},
                  [
                    [:start_element, :top],
                    [:text, ' This is some text.'],
                    [:end_element, :top]
                  ])
  end

  def test_sax_text_no_term
    Ox.default_options = $ox_sax_options
    parse_compare(%{<top>This is some text.},
                  [
                    [:start_element, :top],
                    [:error, 'Not Terminated: text not terminated', 1, 23],
                    [:text, 'This is some text.'],
                    [:error, "Start End Mismatch: element 'top' not closed", 1, 23],
                    [:end_element, :top]
                  ])
  end
  # TBD invalid chacters in text

  def test_sax_doctype
    Ox.default_options = $ox_sax_options
    parse_compare(%{<?xml version="1.0"?>
<!DOCTYPE top PUBLIC "top.dtd">
<top/>
},
                  [
                    [:instruct, 'xml'],
                    [:attr, :version, '1.0'],
                    [:end_instruct, 'xml'],
                    [:doctype, ' top PUBLIC "top.dtd"'],
                    [:start_element, :top],
                    [:end_element, :top]
                  ])
  end

  def test_sax_doctype_big
    Ox.default_options = $ox_sax_options
    parse_compare(%{<?xml version="1.0"?>
<!DOCTYPE Objects [<!ELEMENT Objects (RentObjects)><!ENTITY euml "&#235;"><!ENTITY Atilde "&#195;">]>
<top/>
},
                  [
                    [:instruct, 'xml'],
                    [:attr, :version, '1.0'],
                    [:end_instruct, 'xml'],
                    [:doctype, %{ Objects [<!ELEMENT Objects (RentObjects)><!ENTITY euml "&#235;"><!ENTITY Atilde "&#195;">]}],
                    [:start_element, :top],
                    [:end_element, :top]
                  ])
  end

  def test_sax_doctype_bad_order
    Ox.default_options = $ox_sax_options
    parse_compare(%{<?xml version="1.0"?>
<top/>
<!DOCTYPE top PUBLIC "top.dtd">
},
                  [
                    [:instruct, 'xml'],
                    [:attr, :version, '1.0'],
                    [:end_instruct, 'xml'],
                    [:start_element, :top],
                    [:end_element, :top],
                    [:error, 'Out of Order: DOCTYPE can not come after an element', 3, 10],
                    [:doctype, ' top PUBLIC "top.dtd"']
                  ])
  end

  def test_sax_doctype_space
    Ox.default_options = $ox_sax_options
    parse_compare(%{
<! DOCTYPE top PUBLIC "top.dtd">
<top/>
},
                  [
                    [:error, 'Unexpected Character: <!DOCTYPE can not included spaces', 2, 4],
                    [:doctype, ' top PUBLIC "top.dtd"'],
                    [:start_element, :top],
                    [:end_element, :top]
                  ])
  end

  def test_sax_bad_bang
    Ox.default_options = $ox_sax_options
    parse_compare(%{<top>
  <! Bang Bang>
</top>
},
                  [
                    [:start_element, :top],
                    [:error, 'Unexpected Character: DOCTYPE, CDATA, or comment expected', 2, 6],
                    [:end_element, :top]
                  ])
  end

  def test_sax_comment_simple
    Ox.default_options = $ox_sax_options
    parse_compare(%{<?xml version="1.0"?>
<!--First comment.-->
},
                  [
                    [:instruct, 'xml'],
                    [:attr, :version, '1.0'],
                    [:end_instruct, 'xml'],
                    [:comment, 'First comment.']
                  ])
  end

  def test_sax_comment
    Ox.default_options = $ox_sax_options
    parse_compare(%{<?xml version="1.0"?>
<!--First comment.-->
<top>Before<!--Nested comment.-->After</top>
},
                  [
                    [:instruct, 'xml'],
                    [:attr, :version, '1.0'],
                    [:end_instruct, 'xml'],
                    [:comment, 'First comment.'],
                    [:start_element, :top],
                    [:text, 'Before'],
                    [:comment, 'Nested comment.'],
                    [:text, 'After'],
                    [:end_element, :top]
                  ])
  end

  def test_sax_comment_no_term
    Ox.default_options = $ox_sax_options
    parse_compare(%{<?xml version="1.0"?>
<!--First comment.--
<top/>
},
                  [
                    [:instruct, 'xml'],
                    [:attr, :version, '1.0'],
                    [:end_instruct, 'xml'],
                    [:error, 'Not Terminated: comment not terminated', 3, 0],
                    [:comment, 'First comment.--'],
                    [:start_element, :top],
                    [:end_element, :top]
                  ])
  end

  def test_sax_cdata
    Ox.default_options = $ox_sax_options
    parse_compare(%{<?xml version="1.0"?>
<top>
  <![CDATA[This is CDATA.]]>
</top>
},
                  [
                    [:instruct, 'xml'],
                    [:attr, :version, '1.0'],
                    [:end_instruct, 'xml'],
                    [:start_element, :top],
                    [:cdata, 'This is CDATA.'],
                    [:end_element, :top]
                  ])
  end

  def test_sax_cdata_no_term
    Ox.default_options = $ox_sax_options
    parse_compare(%{<?xml version="1.0"?>
<top>
  <![CDATA[This is CDATA.]]
</top>
},
                  [
                    [:instruct, 'xml'],
                    [:attr, :version, '1.0'],
                    [:end_instruct, 'xml'],
                    [:start_element, :top],
                    [:error, 'Not Terminated: CDATA not terminated', 4, 0],
                    [:cdata, 'This is CDATA.]]'],
                    [:end_element, :top]
                  ])
  end

  def test_sax_cdata_empty
    Ox.default_options = $ox_sax_options
    parse_compare(%{<?xml version="1.0"?>
<top>
  <child><![CDATA[]]></child>
  <child><![CDATA[This is CDATA.]]></child>
</top>
},
                  [
                    [:instruct, 'xml'],
                    [:attr, :version, '1.0'],
                    [:end_instruct, 'xml'],
                    [:start_element, :top],
                    [:start_element, :child],
                    [:cdata, ''],
                    [:end_element, :child],
                    [:start_element, :child],
                    [:cdata, 'This is CDATA.'],
                    [:end_element, :child],
                    [:end_element, :top]
                  ])
  end

  def test_sax_mixed
    Ox.default_options = $ox_sax_options
    parse_compare(%{<?xml version="1.0"?>
<?ox version="1.0" mode="object" circular="no" xsd_date="no"?>
<!DOCTYPE table PUBLIC "-//ox//DTD TABLE 1.0//EN" "http://www.ohler.com/DTDs/TestTable-1.0.dtd">
<table>
  <row id="00004">
    <cell id="A" type="Fixnum">1234</cell>
    <cell id="B" type="String">A string.</cell>
    <cell id="C" type="String">This is a longer string that stretches over a larger number of characters.</cell>
    <cell id="D" type="Float">-12.345</cell>
    <cell id="E" type="Date">2011-09-18 23:07:26 +0900</cell>
    <cell id="F" type="Image"><![CDATA[xx00xx00xx00xx00xx00xx00xx00xx00xx00xx00xx00xx00xx00xx00xx00xx00xx00xx00xx00]]></cell>
  </row>
</table>
},
                  [
                    [:instruct, 'xml'],
                    [:attr, :version, '1.0'],
                    [:end_instruct, 'xml'],
                    [:instruct, 'ox'],
                    [:attr, :version, '1.0'],
                    [:attr, :mode, 'object'],
                    [:attr, :circular, 'no'],
                    [:attr, :xsd_date, 'no'],
                    [:end_instruct, 'ox'],
                    [:doctype, ' table PUBLIC "-//ox//DTD TABLE 1.0//EN" "http://www.ohler.com/DTDs/TestTable-1.0.dtd"'],
                    [:start_element, :table],
                    [:start_element, :row],
                    [:attr, :id, '00004'],
                    [:start_element, :cell],
                    [:attr, :id, 'A'],
                    [:attr, :type, 'Fixnum'],
                    [:text, '1234'],
                    [:end_element, :cell],
                    [:start_element, :cell],
                    [:attr, :id, 'B'],
                    [:attr, :type, 'String'],
                    [:text, 'A string.'],
                    [:end_element, :cell],
                    [:start_element, :cell],
                    [:attr, :id, 'C'],
                    [:attr, :type, 'String'],
                    [:text, 'This is a longer string that stretches over a larger number of characters.'],
                    [:end_element, :cell],
                    [:start_element, :cell],
                    [:attr, :id, 'D'],
                    [:attr, :type, 'Float'],
                    [:text, '-12.345'],
                    [:end_element, :cell],
                    [:start_element, :cell],
                    [:attr, :id, 'E'],
                    [:attr, :type, 'Date'],
                    [:text, '2011-09-18 23:07:26 +0900'],
                    [:end_element, :cell],
                    [:start_element, :cell],
                    [:attr, :id, 'F'],
                    [:attr, :type, 'Image'],
                    [:cdata, 'xx00xx00xx00xx00xx00xx00xx00xx00xx00xx00xx00xx00xx00xx00xx00xx00xx00xx00xx00'],
                    [:end_element, :cell],
                    [:end_element, :row],
                    [:end_element, :table]
                  ])
  end

  def test_sax_encoding
    Ox.default_options = $ox_sax_options
    parse_compare(%{<?xml version="1.0" encoding="UTF-8"?>
<top>ピーター</top>
},
                  [
                    [:instruct, 'xml'],
                    [:attr, :version, '1.0'],
                    [:attr, :encoding, 'UTF-8'],
                    [:end_instruct, 'xml'],
                    [:start_element, :top],
                    [:text, 'ピーター'],
                    [:end_element, :top]
                  ])
  end

  def test_sax_bom
    Ox.default_options = $ox_sax_options
    xml = %{\xEF\xBB\xBF<?xml?>
<top>ピーター</top>
}
    xml.force_encoding('ASCII') unless RUBY_VERSION.start_with?('1.8')
    parse_compare(xml,
                  [
                    [:instruct, 'xml'],
                    [:end_instruct, 'xml'],
                    [:start_element, :top],
                    [:text, 'ピーター'],
                    [:end_element, :top]
                  ])
  end

  def test_sax_full_encoding
    Ox.default_options = $ox_sax_options
    parse_compare(%{<?xml version="1.0" encoding="UTF-8"?>
<いち name="ピーター" つま="まきえ">ピーター is katakana</いち>
},
                  [
                    [:instruct, 'xml'],
                    [:attr, :version, '1.0'],
                    [:attr, :encoding, 'UTF-8'],
                    [:end_instruct, 'xml'],
                    [:start_element, :いち],
                    [:attr, :name, 'ピーター'],
                    [:attr, :つま, 'まきえ'],
                    [:text, 'ピーター is katakana'],
                    [:end_element, :いち]
                  ])
  end

  def test_sax_amp_hash
    Ox.default_options = $ox_sax_options
    parse_compare(%{<?xml version="1.0" encoding="UTF-8"?>
<text>&#233; is e with an accent</text>
},
                  [
                    [:instruct, 'xml'],
                    [:attr, :version, '1.0'],
                    [:attr, :encoding, 'UTF-8'],
                    [:end_instruct, 'xml'],
                    [:start_element, :text],
                    [:text, 'é is e with an accent'],
                    [:end_element, :text]
                  ], AllSax, convert_special: true)
  end

  def test_sax_implied_encoding
    Ox.default_options = $ox_sax_options
    if RUBY_VERSION.start_with?('1.8') || RUBY_DESCRIPTION.start_with?('rubinius 2.0.0dev')
      assert(true)
    else
      xml = %{<?xml version="1.0"?>
<top>ピーター</top>
}
      xml.force_encoding('UTF-8')
      handler = AllSax.new
      input = StringIO.new(xml)
      Ox.sax_parse(handler, input)
      assert_equal('UTF-8', handler.calls.assoc(:text)[1].encoding.to_s)
      # now try a different one
      xml.force_encoding('US-ASCII')
      handler = AllSax.new
      input = StringIO.new(xml)
      Ox.sax_parse(handler, input)
      assert_equal('US-ASCII', handler.calls.assoc(:text)[1].encoding.to_s)
    end
  end

  def test_sax_non_utf8_encoding
    Ox.default_options = $ox_sax_options
    if RUBY_VERSION.start_with?('1.8') || RUBY_DESCRIPTION.start_with?('rubinius 2.0.0dev')
      assert(true)
    else
      xml = %{<?xml version="1.0" encoding="Windows-1251"?>
<top>тест</top>
}
      handler = AllSax.new
      input = StringIO.new(xml)
      Ox.sax_parse(handler, input)

      content = handler.calls.assoc(:text)[1]
      assert_equal('Windows-1251', content.encoding.to_s)
      assert_equal('тест'.force_encoding('Windows-1251'), content)
    end
  end

  def test_sax_value_fixnum
    Ox.default_options = $ox_sax_options
    handler = TypeSax.new(:as_i)
    Ox.sax_parse(handler, StringIO.new(%{<top>7</top>}))
    assert_equal(7, handler.item)
  end

  def test_sax_value_string
    Ox.default_options = $ox_sax_options
    handler = TypeSax.new(:as_s)
    Ox.sax_parse(handler, StringIO.new(%{<top>cheese</top>}))
    assert_equal('cheese', handler.item)
  end

  def test_sax_skip_none
    Ox.default_options = $ox_sax_options
    Ox.default_options = { skip: :skip_none }

    handler = TypeSax.new(:as_s)
    xml = %{<top>  Pete\r\n  Ohler\r</top>}
    Ox.sax_parse(handler, StringIO.new(xml))
    assert_equal(%{  Pete\n  Ohler\n}, handler.item)
  end

  def test_sax_skip_none_nested
    Ox.default_options = $ox_sax_options
    Ox.default_options = { skip: :skip_none }

    handler = AllSax.new
    xml = %{<top>  <child>Pete\r</child>\n <child> Ohler\r</child></top>}
    Ox.sax_parse(handler, StringIO.new(xml))
    assert_equal([
                   [:start_element, :top],
                   [:text, '  '],
                   [:start_element, :child],
                   [:text, "Pete\n"],
                   [:end_element, :child],
                   [:text, "\n "],
                   [:start_element, :child],
                   [:text, " Ohler\n"],
                   [:end_element, :child],
                   [:end_element, :top]
                 ], handler.calls)
  end

  def test_sax_skip_return
    Ox.default_options = $ox_sax_options
    handler = TypeSax.new(:as_s)
    xml = %{<top>  Pete\r\n  Ohler\r</top>}
    Ox.sax_parse(handler, StringIO.new(xml), skip: :skip_return)
    assert_equal(%{  Pete\n  Ohler\n}, handler.item)
  end

  def test_sax_skip_white
    Ox.default_options = $ox_sax_options
    handler = TypeSax.new(:as_s)
    xml = %{<top>  Pete\r\n  Ohler\r</top>}
    Ox.sax_parse(handler, StringIO.new(xml), skip: :skip_white)
    assert_equal(%{ Pete Ohler }, handler.item)
  end

  def test_sax_value_float
    Ox.default_options = $ox_sax_options
    handler = TypeSax.new(:as_f)
    Ox.sax_parse(handler, StringIO.new(%{<top>7</top>}))
    assert_equal(7.0, handler.item)
  end

  def test_sax_value_sym
    Ox.default_options = $ox_sax_options
    handler = TypeSax.new(:as_sym)
    Ox.sax_parse(handler, StringIO.new(%{<top>cheese</top>}))
    assert_equal(:cheese, handler.item)
  end

  def test_sax_value_bool
    Ox.default_options = $ox_sax_options
    handler = TypeSax.new(:as_bool)
    Ox.sax_parse(handler, StringIO.new(%{<top>true</top>}))
    assert_equal(true, handler.item)
  end

  def test_sax_value_nil
    Ox.default_options = $ox_sax_options
    handler = TypeSax.new(:as_i)
    Ox.sax_parse(handler, StringIO.new(%{<top></top>}))
    assert_equal(nil, handler.item)
  end

  def test_sax_value_time
    Ox.default_options = $ox_sax_options
    t = Time.local(2012, 1, 5, 10, 20, 30)
    handler = TypeSax.new(:as_time)
    Ox.sax_parse(handler, StringIO.new(%{<top>#{t}</top>}))
    assert_equal(t.sec, handler.item.sec)
    assert_equal(t.usec, handler.item.usec)
    t = Time.now
    Ox.sax_parse(handler, StringIO.new(format(%{<top>%d.%06d</top>}, t.sec, t.usec)))
    assert_equal(t.sec, handler.item.sec)
    assert_equal(t.usec, handler.item.usec)
  end

  def test_sax_attr_value_fixnum
    Ox.default_options = $ox_sax_options
    handler = TypeSax.new(nil)
    Ox.sax_parse(handler, StringIO.new(%{<top as_i="7"/>}))
    assert_equal(7, handler.item)
  end

  def test_sax_attr_value_string
    Ox.default_options = $ox_sax_options
    handler = TypeSax.new(nil)
    Ox.sax_parse(handler, StringIO.new(%{<top as_s="cheese"/>}))
    assert_equal('cheese', handler.item)
  end

  def test_sax_attr_value_float
    Ox.default_options = $ox_sax_options
    handler = TypeSax.new(nil)
    Ox.sax_parse(handler, StringIO.new(%{<top as_f="7"/>}))
    assert_equal(7.0, handler.item)
  end

  def test_sax_attr_value_sym
    Ox.default_options = $ox_sax_options
    handler = TypeSax.new(nil)
    Ox.sax_parse(handler, StringIO.new(%{<top as_sym="cheese"/>}))
    assert_equal(:cheese, handler.item)
  end

  def test_sax_attr_value_bool
    Ox.default_options = $ox_sax_options
    handler = TypeSax.new(nil)
    Ox.sax_parse(handler, StringIO.new(%{<top as_bool="true"/>}))
    assert_equal(true, handler.item)
  end

  def test_sax_attr_value_nil
    Ox.default_options = $ox_sax_options
    handler = TypeSax.new(nil)
    Ox.sax_parse(handler, StringIO.new(%{<top as_i=""/>}))
    assert_equal(nil, handler.item)
  end

  def test_sax_attr_value_time
    Ox.default_options = $ox_sax_options
    t = Time.local(2012, 1, 5, 10, 20, 30)
    handler = TypeSax.new(nil)
    Ox.sax_parse(handler, StringIO.new(%{<top as_time="#{t}"/>}))
    assert_equal(t.sec, handler.item.sec)
    assert_equal(t.usec, handler.item.usec)
    t = Time.local(2012, 1, 5, 10, 20, 30.123456)
    Ox.sax_parse(handler, StringIO.new(format(%{<top as_time="%d.%06d"/>}, t.sec, t.usec)))
    assert_equal(t.sec, handler.item.sec)
    assert_equal(t.usec, handler.item.usec)
  end

  def test_sax_nested_same_prefix
    Ox.default_options = $ox_sax_options
    handler = ErrorSax.new
    Ox.sax_parse(handler, StringIO.new(%{<?xml version="1.0"?><abc><abcd/></abc>}))
    assert_equal([], handler.errors)
    Ox.sax_parse(handler, StringIO.new(%{<?xml version="1.0"?><abc><ab/></abc>}))
    assert_equal([], handler.errors)
    Ox.sax_parse(handler, StringIO.new(%{<?xml version="1.0"?><abcd><abcd/></abcd>}))
    assert_equal([], handler.errors)
    Ox.sax_parse(handler, StringIO.new(%{<?xml version="1.0"?><ab><a/></ab>}))
    assert_equal([], handler.errors)
    Ox.sax_parse(handler, StringIO.new(%{<?xml version="1.0"?><abcdefghijklmnop></abcdefghijklmnop>}))
    assert_equal([], handler.errors)
  end

  def test_sax_offset_start
    Ox.default_options = $ox_sax_options
    handler = AllSax.new
    xml = StringIO.new(%|
this is not part of the xml document
<top>this is xml</top>
|)
    xml.seek(37)
    Ox.sax_parse(handler, xml)
    assert_equal([
                   [:start_element, :top],
                   [:text, 'this is xml'],
                   [:end_element, :top]
                 ], handler.calls)
  end

  def test_sax_tolerant
    Ox.default_options = $ox_sax_options
    xml = %{<!doctype HTML>
<html lang=en>
  <head garbage='trash'>
    <bad attr="some&#xthing">
    <bad alone>
  </head>
  <body>
  This is a test of the &tolerant&# effort option.
  </body>
</html>
<p>after thought</p>
}
    parse_compare(xml,
                  [
                    [:doctype, ' HTML'],
                    [:start_element, :html],
                    [:error, 'Unexpected Character: attribute value not in quotes', 2, 12],
                    [:attr, :lang, 'en'],
                    [:start_element, :head],
                    [:attr, :garbage, 'trash'],
                    [:error, 'Invalid Element: bad is not a valid element type for a HTML document type.', 4, 9],
                    [:start_element, :bad],
                    [:attr, :attr, 'some&#xthing'],
                    [:error, 'Invalid Element: bad is not a valid element type for a HTML document type.', 5, 9],
                    [:start_element, :bad],
                    [:error, 'Unexpected Character: no attribute value', 5, 15],
                    [:attr, :alone, ''],
                    [:text, "\n  "],
                    [:error, "Start End Mismatch: element 'head' close does not match 'bad' open", 6, 3],
                    [:end_element, :bad],
                    [:end_element, :bad],
                    [:end_element, :head],
                    [:start_element, :body],
                    [:text, "\n  This is a test of the &tolerant&# effort option.\n  "],
                    [:end_element, :body],
                    [:end_element, :html],
                    [:error, 'Out of Order: multiple top level elements', 11, 1],
                    [:start_element, :p],
                    [:text, 'after thought'],
                    [:end_element, :p]
                  ],
                  AllSax, convert_special: false, smart: true)
  end

  def test_sax_not_symbolized
    Ox.default_options = $ox_sax_options
    xml = %{<!DOCTYPE HTML>
<html lang="en">
  <head>
  </head>
  <body>
  This is a some text.
  </body>
</html>
}
    parse_compare(xml,
                  [
                    [:doctype, ' HTML'],
                    [:start_element, 'html'],
                    [:attr, 'lang', 'en'],
                    [:start_element, 'head'],
                    [:text, "\n  "],
                    [:end_element, 'head'],
                    [:start_element, 'body'],
                    [:text, "\n  This is a some text.\n  "],
                    [:end_element, 'body'],
                    [:end_element, 'html']
                  ],
                  AllSax, symbolize: false, smart: true)
  end

  def test_sax_script
    Ox.default_options = $ox_sax_options
    html = %|<!DOCTYPE HTML>
<html lang="en">
  <head>
  </head>
  <body>
    <script type='text/javascript'>
      def fake(c,d) {
        x = "</script";
        return c<d;
      }
    </ script >
  </body>
</html>
|
    parse_compare(html,
                  [
                    [:doctype, ' HTML'],
                    [:start_element, :html],
                    [:attr, :lang, 'en'],
                    [:start_element, :head],
                    [:text, "\n  "],
                    [:end_element, :head],
                    [:start_element, :body],
                    [:start_element, :script],
                    [:attr, :type, 'text/javascript'],
                    [:text, "\n      def fake(c,d) {\n        x = \"</script\";\n        return c<d;\n      }\n    "],
                    [:end_element, :script],
                    [:end_element, :body],
                    [:end_element, :html]
                  ],
                  AllSax, smart: true)
  end

  def test_sax_namespace_no_strip
    Ox.default_options = $ox_sax_options
    handler = AllSax.new
    Ox.sax_parse(handler, '<spaced:one><two spaced:out="no" other:space="yes">inside</two></spaced:one>')
    assert_equal([
                   [:start_element, :"spaced:one"],
                   [:start_element, :two],
                   [:attr, :"spaced:out", 'no'],
                   [:attr, :"other:space", 'yes'],
                   [:text, 'inside'],
                   [:end_element, :two],
                   [:end_element, :"spaced:one"]
                 ], handler.calls)
  end

  def test_sax_namespace_strip_wild
    Ox.default_options = $ox_sax_options
    handler = AllSax.new
    Ox.sax_parse(handler,
                 '<spaced:one><two spaced:out="no" other:space="yes">inside</two></spaced:one>',
                 strip_namespace: true)
    assert_equal([
                   [:start_element, :one],
                   [:start_element, :two],
                   [:attr, :out, 'no'],
                   [:attr, :space, 'yes'],
                   [:text, 'inside'],
                   [:end_element, :two],
                   [:end_element, :one]
                 ], handler.calls)
  end

  def test_sax_namespace_strip
    Ox.default_options = $ox_sax_options
    Ox.default_options = { strip_namespace: 'spaced' }

    handler = AllSax.new
    Ox.sax_parse(handler, '<spaced:one><two spaced:out="no" other:space="yes">inside</two></spaced:one>')
    assert_equal([
                   [:start_element, :one],
                   [:start_element, :two],
                   [:attr, :out, 'no'],
                   [:attr, :"other:space", 'yes'],
                   [:text, 'inside'],
                   [:end_element, :two],
                   [:end_element, :one]
                 ], handler.calls)
  end

  def test_sax_html_inactive
    Ox.default_options = $ox_sax_options
    handler = AllSax.new

    Ox.sax_html(handler, '<html><h1>title</h1><hr/><p>Hello</p></html>', overlay: { 'hr'=>:inactive })
    assert_equal([
                   [:start_element, :html],
                   [:start_element, :h1],
                   [:text, 'title'],
                   [:end_element, :h1],
                   [:start_element, :p],
                   [:text, 'Hello'],
                   [:end_element, :p],
                   [:end_element, :html]
                 ], handler.calls)
  end

  def test_sax_html_overlay_mod
    Ox.default_options = $ox_sax_options
    handler = AllSax.new
    overlay = Ox.sax_html_overlay
    overlay['hr'] = :inactive
    Ox.sax_html(handler, '<html><h1>title</h1><hr/><p>Hello</p></html>', overlay: overlay)
    assert_equal([
                   [:start_element, :html],
                   [:start_element, :h1],
                   [:text, 'title'],
                   [:end_element, :h1],
                   [:start_element, :p],
                   [:text, 'Hello'],
                   [:end_element, :p],
                   [:end_element, :html]
                 ], handler.calls)
  end

  def test_sax_html_active
    Ox.default_options = $ox_sax_options
    handler = AllSax.new
    overlay = Ox.sax_html_overlay
    overlay.each_key { |k| overlay[k] = :inactive }
    overlay['h1'] = :active
    overlay['p'] = :active

    Ox.sax_html(handler, '<html class="fuzzy"><h1>title</h1><hr/><p>Hello</p></html>', overlay: overlay)
    assert_equal([
                   [:start_element, :h1],
                   [:text, 'title'],
                   [:end_element, :h1],
                   [:start_element, :p],
                   [:text, 'Hello'],
                   [:end_element, :p]
                 ], handler.calls)
  end

  def test_sax_html_default_overlay_mod
    Ox.default_options = $ox_sax_options
    handler = AllSax.new
    overlay = Ox.sax_html_overlay
    overlay['hr'] = :inactive
    Ox.default_options = { overlay: overlay }
    Ox.sax_html(handler, '<html><h1>title</h1><hr/><p>Hello</p></html>')
    assert_equal([
                   [:start_element, :html],
                   [:start_element, :h1],
                   [:text, 'title'],
                   [:end_element, :h1],
                   [:start_element, :p],
                   [:text, 'Hello'],
                   [:end_element, :p],
                   [:end_element, :html]
                 ], handler.calls)
  end

  def test_sax_html_block
    Ox.default_options = $ox_sax_options
    handler = AllSax.new
    overlay = Ox.sax_html_overlay
    overlay['table'] = :block

    Ox.sax_html(handler, '<html><h1>title</h1><table><!--comment--><tr><td>one</td></tr></table></html>', overlay: overlay)
    assert_equal([
                   [:start_element, :html],
                   [:start_element, :h1],
                   [:text, 'title'],
                   [:end_element, :h1],
                   [:end_element, :html]
                 ], handler.calls)
  end

  def test_sax_html_off
    Ox.default_options = $ox_sax_options
    handler = AllSax.new
    overlay = Ox.sax_html_overlay
    overlay['table'] = :off
    overlay['td'] = :off
    overlay['!--'] = :off

    Ox.sax_html(handler, '<html><h1>title</h1><table><!--comment--><hr/><tr><td>one</td></tr></table></html>', overlay: overlay)
    assert_equal([
                   [:start_element, :html],
                   [:start_element, :h1],
                   [:text, 'title'],
                   [:end_element, :h1],
                   [:start_element, :hr],
                   [:end_element, :hr],
                   [:start_element, :tr],
                   [:end_element, :tr],
                   [:end_element, :html]
                 ], handler.calls)
  end

  def test_sax_html_inactive_full_map
    Ox.default_options = $ox_sax_options
    handler = AllSax.new
    overlay = Ox.sax_html_overlay
    overlay['table'] = :inactive
    overlay['td'] = :inactive

    Ox.sax_html(handler, '<html><h1>title</h1><table><!--comment--><tr><td>one</td></tr></table></html>', overlay: overlay)
    assert_equal([
                   [:start_element, :html],
                   [:start_element, :h1],
                   [:text, 'title'],
                   [:end_element, :h1],
                   [:comment, 'comment'],
                   [:start_element, :tr],
                   [:text, 'one'],
                   [:end_element, :tr],
                   [:end_element, :html]
                 ], handler.calls)
  end

  def test_sax_html_abort
    Ox.default_options = $ox_sax_options
    handler = AllSax.new
    overlay = Ox.sax_html_overlay
    overlay['table'] = :abort

    Ox.sax_html(handler, '<html><h1>title</h1><table><!--comment--><tr><td>one</td></tr></table></html>', overlay: overlay)
    assert_equal([
                   [:start_element, :html],
                   [:start_element, :h1],
                   [:text, 'title'],
                   [:end_element, :h1],
                   [:abort, :table]
                 ], handler.calls)
  end

  def test_sax_html_attr
    Ox.default_options = $ox_sax_options
    handler = AllSax.new
    overlay = Ox.sax_html_overlay
    html = %{<!doctype HTML>
<html lang=en>
  <head url=http://ohler.com?x=2>
  </head>
</html>
}
    Ox.sax_html(handler, html, overlay: overlay, skip: :skip_white)
    assert_equal([
                   [:doctype, ' HTML'],
                   [:start_element, :html],
                   [:attr, :lang, 'en'],
                   [:start_element, :head],
                   [:attr, :url, 'http://ohler.com?x=2'],
                   [:text, ' '],
                   [:end_element, :head],
                   [:end_element, :html]
                 ], handler.calls)
  end
end
