I can't emphasize this enough, because people do have problems with
+- this. REXML can't possibly alway guess correctly how your text is
++ this. REXML can't possibly always guess correctly how your text is
+ encoded, so it always assumes the text is UTF-8. It also does not warn
+ you when you try to add text which isn't properly encoded, for the
+ same reason. You must make sure that you are adding UTF-8 text.
+diff --git a/test/rexml/formatter/test_default.rb b/test/rexml/formatter/test_default.rb
+new file mode 100644
+index 0000000..aa403db
+--- /dev/null
++++ b/test/rexml/formatter/test_default.rb
+@@ -0,0 +1,17 @@
++module REXMLTests
++ class DefaultFormatterTest < Test::Unit::TestCase
++ def format(node)
++ formatter = REXML::Formatters::Default.new
++ output = +""
++ formatter.write(node, output)
++ output
++ end
++
++ class InstructionTest < self
++ def test_content_nil
++ instruction = REXML::Instruction.new("target")
++ assert_equal("", format(instruction))
++ end
++ end
++ end
++end
+diff --git a/test/rexml/test_functions.rb b/test/rexml/functions/test_base.rb
+similarity index 87%
+rename from test/rexml/test_functions.rb
+rename to test/rexml/functions/test_base.rb
+index a77be38..daa3815 100644
+--- a/test/rexml/test_functions.rb
++++ b/test/rexml/functions/test_base.rb
+@@ -3,9 +3,16 @@ require "test/unit/testcase"
+
+ require "rexml/document"
+
++# TODO: Split me
+ module REXMLTests
+ class FunctionsTester < Test::Unit::TestCase
+ include REXML
++
++ def setup
++ super
++ REXML::Functions.context = nil
++ end
++
+ def test_functions
+ # trivial text() test
+ # confuse-a-function
+@@ -222,6 +229,44 @@ module REXMLTests
+ assert_equal( [REXML::Comment.new("COMMENT A")], m )
+ end
+
++ def test_normalize_space_strings
++ source = <<-XML
++breakfast boosts\t\t
++
++concentration
++Coffee beans
++ aroma
++
++
++
++ Dessert
++ \t\t after dinner
++ XML
++ normalized_texts = REXML::XPath.each(REXML::Document.new(source), "normalize-space(//text())").to_a
++ assert_equal([
++ "breakfast boosts concentration",
++ "Coffee beans aroma",
++ "Dessert after dinner",
++ ],
++ normalized_texts)
++ end
++
++ def test_string_nil_without_context
++ doc = REXML::Document.new(<<~XML)
++
++
++
++
++
++ XML
++
++ assert_equal([doc.root.elements[2]],
++ REXML::XPath.match(doc,
++ "//foo[@bar=$n]",
++ nil,
++ {"n" => nil}))
++ end
++
+ def test_unregistered_method
+ doc = Document.new("")
+ assert_nil(XPath::first(doc.root, "to_s()"))
+diff --git a/test/rexml/functions/test_boolean.rb b/test/rexml/functions/test_boolean.rb
+new file mode 100644
+index 0000000..b3e2117
+--- /dev/null
++++ b/test/rexml/functions/test_boolean.rb
+@@ -0,0 +1,75 @@
++# frozen_string_literal: false
++
++require "test/unit"
++require "rexml/document"
++require "rexml/functions"
++
++module REXMLTests
++ class TestFunctionsBoolean < Test::Unit::TestCase
++ def setup
++ REXML::Functions.context = nil
++ end
++
++ def test_true
++ assert_equal(true, REXML::Functions.boolean(true))
++ end
++
++ def test_false
++ assert_equal(false, REXML::Functions.boolean(false))
++ end
++
++ def test_integer_true
++ assert_equal(true, REXML::Functions.boolean(1))
++ end
++
++ def test_integer_positive_zero
++ assert_equal(false, REXML::Functions.boolean(0))
++ end
++
++ def test_integer_negative_zero
++ assert_equal(false, REXML::Functions.boolean(-0))
++ end
++
++ def test_float_true
++ assert_equal(true, REXML::Functions.boolean(1.1))
++ end
++
++ def test_float_positive_zero
++ assert_equal(false, REXML::Functions.boolean(-0.0))
++ end
++
++ def test_float_negative_zero
++ assert_equal(false, REXML::Functions.boolean(-0.0))
++ end
++
++ def test_float_nan
++ assert_equal(false, REXML::Functions.boolean(Float::NAN))
++ end
++
++ def test_string_true
++ assert_equal(true, REXML::Functions.boolean("content"))
++ end
++
++ def test_string_empty
++ assert_equal(false, REXML::Functions.boolean(""))
++ end
++
++ def test_node_set_true
++ root = REXML::Document.new("").root
++ assert_equal(true, REXML::Functions.boolean([root]))
++ end
++
++ def test_node_set_empty
++ assert_equal(false, REXML::Functions.boolean([]))
++ end
++
++ def test_nil
++ assert_equal(false, REXML::Functions.boolean(nil))
++ end
++
++ def test_context
++ REXML::Functions.context = {node: true}
++ assert_equal(true, REXML::Functions.boolean())
++ end
++ end
++end
+diff --git a/test/rexml/functions/test_local_name.rb b/test/rexml/functions/test_local_name.rb
+new file mode 100644
+index 0000000..97c9e74
+--- /dev/null
++++ b/test/rexml/functions/test_local_name.rb
+@@ -0,0 +1,44 @@
++# frozen_string_literal: false
++
++require "test/unit"
++require "rexml/document"
++require "rexml/functions"
++
++module REXMLTests
++ class TestFunctionsLocalName < Test::Unit::TestCase
++ def setup
++ REXML::Functions.context = nil
++ end
++
++ def test_one
++ document = REXML::Document.new(<<-XML)
++
++
++
++ XML
++ node_set = document.root.children
++ assert_equal("child", REXML::Functions.local_name(node_set))
++ end
++
++ def test_multiple
++ document = REXML::Document.new(<<-XML)
++
++
++
++
++ XML
++ node_set = document.root.children
++ assert_equal("child1", REXML::Functions.local_name(node_set))
++ end
++
++ def test_nonexistent
++ assert_equal("", REXML::Functions.local_name([]))
++ end
++
++ def test_context
++ document = REXML::Document.new("")
++ REXML::Functions.context = {node: document.root}
++ assert_equal("root", REXML::Functions.local_name())
++ end
++ end
++end
+diff --git a/test/rexml/functions/test_number.rb b/test/rexml/functions/test_number.rb
+new file mode 100644
+index 0000000..16e6357
+--- /dev/null
++++ b/test/rexml/functions/test_number.rb
+@@ -0,0 +1,38 @@
++# frozen_string_literal: false
++
++require "test/unit"
++require "rexml/document"
++require "rexml/functions"
++
++module REXMLTests
++ class TestFunctionsNumber < Test::Unit::TestCase
++ def setup
++ REXML::Functions.context = nil
++ end
++
++ def test_true
++ assert_equal(1, REXML::Functions.number(true))
++ end
++
++ def test_false
++ assert_equal(0, REXML::Functions.number(false))
++ end
++
++ def test_numeric
++ assert_equal(29, REXML::Functions.number(29))
++ end
++
++ def test_string_integer
++ assert_equal(100, REXML::Functions.number("100"))
++ end
++
++ def test_string_float
++ assert_equal(-9.13, REXML::Functions.number("-9.13"))
++ end
++
++ def test_node_set
++ root = REXML::Document.new("100").root
++ assert_equal(100, REXML::Functions.number([root]))
++ end
++ end
++end
+diff --git a/test/rexml/helper.rb b/test/rexml/helper.rb
+new file mode 100644
+index 0000000..3de1327
+--- /dev/null
++++ b/test/rexml/helper.rb
+@@ -0,0 +1,35 @@
++# frozen_string_literal: false
++
++require "test-unit"
++
++require "rexml/document"
++
++module Helper
++ module Fixture
++ def fixture_path(*components)
++ File.join(__dir__, "data", *components)
++ end
++ end
++
++ module Global
++ def suppress_warning
++ verbose = $VERBOSE
++ begin
++ $VERBOSE = nil
++ yield
++ ensure
++ $VERBOSE = verbose
++ end
++ end
++
++ def with_default_internal(encoding)
++ default_internal = Encoding.default_internal
++ begin
++ suppress_warning {Encoding.default_internal = encoding}
++ yield
++ ensure
++ suppress_warning {Encoding.default_internal = default_internal}
++ end
++ end
++ end
++end
+diff --git a/test/rexml/parse/test_comment.rb b/test/rexml/parse/test_comment.rb
+new file mode 100644
+index 0000000..ce6678e
+--- /dev/null
++++ b/test/rexml/parse/test_comment.rb
+@@ -0,0 +1,109 @@
++require "test/unit"
++require "rexml/document"
++
++module REXMLTests
++ class TestParseComment < Test::Unit::TestCase
++ def parse(xml)
++ REXML::Document.new(xml)
++ end
++
++ class TestInvalid < self
++ def test_toplevel_unclosed_comment
++ exception = assert_raise(REXML::ParseException) do
++ parse("")
++ end
++ assert_equal(<<~DETAIL, exception.to_s)
++ Malformed comment
++ Line: 1
++ Position: 11
++ Last 80 unconsumed characters:
++ DETAIL
++ end
++
++ def test_toplevel_malformed_comment_end
++ exception = assert_raise(REXML::ParseException) do
++ parse("")
++ end
++ assert_equal(<<~DETAIL, exception.to_s)
++ Malformed comment
++ Line: 1
++ Position: 9
++ Last 80 unconsumed characters:
++ DETAIL
++ end
++
++ def test_doctype_malformed_comment_inner
++ exception = assert_raise(REXML::ParseException) do
++ parse("")
++ end
++ assert_equal(<<~DETAIL, exception.to_s)
++ Malformed comment
++ Line: 1
++ Position: 26
++ Last 80 unconsumed characters:
++ DETAIL
++ end
++
++ def test_doctype_malformed_comment_end
++ exception = assert_raise(REXML::ParseException) do
++ parse("")
++ end
++ assert_equal(<<~DETAIL, exception.to_s)
++ Malformed comment
++ Line: 1
++ Position: 24
++ Last 80 unconsumed characters:
++ DETAIL
++ end
++
++ def test_after_doctype_malformed_comment_short
++ exception = assert_raise(REXML::ParseException) do
++ parse("")
++ end
++ assert_equal(<<~DETAIL.chomp, exception.to_s)
++ Malformed comment
++ Line: 1
++ Position: 8
++ Last 80 unconsumed characters:
++ -->
++ DETAIL
++ end
++
++ def test_after_doctype_malformed_comment_inner
++ exception = assert_raise(REXML::ParseException) do
++ parse("")
++ end
++ assert_equal(<<~DETAIL, exception.to_s)
++ Malformed comment
++ Line: 1
++ Position: 14
++ Last 80 unconsumed characters:
++ DETAIL
++ end
++
++ def test_after_doctype_malformed_comment_end
++ exception = assert_raise(REXML::ParseException) do
++ parse("")
++ end
++ assert_equal(<<~DETAIL, exception.to_s)
++ Malformed comment
++ Line: 1
++ Position: 12
++ Last 80 unconsumed characters:
++ DETAIL
++ end
++ end
++ end
++end
+diff --git a/test/rexml/parse/test_document_type_declaration.rb b/test/rexml/parse/test_document_type_declaration.rb
+index 5571390..3ca0b53 100644
+--- a/test/rexml/parse/test_document_type_declaration.rb
++++ b/test/rexml/parse/test_document_type_declaration.rb
+@@ -36,6 +36,66 @@ Last 80 unconsumed characters:
+ + r SYSTEM "urn:x-rexml:test" [ ]>
+ DETAIL
+ end
++
++ def test_no_name
++ exception = assert_raise(REXML::ParseException) do
++ parse(<<-DOCTYPE)
++
++ DOCTYPE
++ end
++ assert_equal(<<-DETAIL.chomp, exception.to_s)
++Malformed DOCTYPE: name is missing
++Line: 3
++Position: 17
++Last 80 unconsumed characters:
++
++ DETAIL
++ end
++ end
++
++ class TestUnclosed < self
++ def test_no_extra_node
++ exception = assert_raise(REXML::ParseException) do
++ REXML::Document.new("
++ DOCTYPE
++ end
++ assert_equal(<<~DETAIL.chomp, exception.to_s)
++ Malformed DOCTYPE: invalid declaration
++ Line: 1
++ Position: 20
++ Last 80 unconsumed characters:
++ #{' '}
++ DETAIL
++ end
++
++ def test_text
++ exception = assert_raise(REXML::ParseException) do
++ REXML::Document.new(<<~DOCTYPE)
++
+ Line: 1
+ Position: 13
+ Last 80 unconsumed characters:
++:a="">
++ DETAIL
++ end
+
++ def test_empty_namespace_attribute_name_with_utf8_character
++ exception = assert_raise(REXML::ParseException) do
++ parse("") # U+200B ZERO WIDTH SPACE
++ end
++ assert_equal(<<-DETAIL.chomp.force_encoding("ASCII-8BIT"), exception.to_s)
++Invalid attribute name: <:\xE2\x80\x8B>
++Line: 1
++Position: 8
++Last 80 unconsumed characters:
++:\xE2\x80\x8B>
+ DETAIL
+ end
+
+diff --git a/test/rexml/parse/test_entity_declaration.rb b/test/rexml/parse/test_entity_declaration.rb
+new file mode 100644
+index 0000000..e15deec
+--- /dev/null
++++ b/test/rexml/parse/test_entity_declaration.rb
+@@ -0,0 +1,36 @@
++# frozen_string_literal: false
++require 'test/unit'
++require 'rexml/document'
++
++module REXMLTests
++ class TestParseEntityDeclaration < Test::Unit::TestCase
++ private
++ def xml(internal_subset)
++ <<-XML
++
++
++ XML
++ end
++
++ def parse(internal_subset)
++ REXML::Document.new(xml(internal_subset)).doctype
++ end
++
++ def test_empty
++ exception = assert_raise(REXML::ParseException) do
++ parse(<<-INTERNAL_SUBSET)
++
++ INTERNAL_SUBSET
++ end
++ assert_equal(<<-DETAIL.chomp, exception.to_s)
++Malformed notation declaration: name is missing
++Line: 5
++Position: 72
++Last 80 unconsumed characters:
++ ]>
++ DETAIL
++ end
++ end
++end
+diff --git a/test/rexml/parse/test_notation_declaration.rb b/test/rexml/parse/test_notation_declaration.rb
+index 19a0536..9e81b6a 100644
+--- a/test/rexml/parse/test_notation_declaration.rb
++++ b/test/rexml/parse/test_notation_declaration.rb
+@@ -35,7 +35,7 @@ Malformed notation declaration: name is missing
+ Line: 5
+ Position: 72
+ Last 80 unconsumed characters:
+- ]>
++ ]>
+ DETAIL
+ end
+
+diff --git a/test/rexml/parse/test_processing_instruction.rb b/test/rexml/parse/test_processing_instruction.rb
+new file mode 100644
+index 0000000..f0c0c24
+--- /dev/null
++++ b/test/rexml/parse/test_processing_instruction.rb
+@@ -0,0 +1,44 @@
++require "test/unit"
++require "rexml/document"
++
++module REXMLTests
++ class TestParseProcessinInstruction < Test::Unit::TestCase
++ def parse(xml)
++ REXML::Document.new(xml)
++ end
++
++ class TestInvalid < self
++ def test_no_name
++ exception = assert_raise(REXML::ParseException) do
++ parse("?>")
++ end
++ assert_equal(<<-DETAIL.chomp, exception.to_s)
++Invalid processing instruction node
++Line: 1
++Position: 4
++Last 80 unconsumed characters:
++?>
++ DETAIL
++ end
++
++ def test_garbage_text
++ # TODO: This should be parse error.
++ # Create test/parse/test_document.rb or something and move this to it.
++ doc = parse(<<-XML)
++x?>
++
++ XML
++ pi = doc.children[1]
++ assert_equal([
++ "x",
++ "y\n
+@@ -379,7 +377,7 @@ EOF
+ end
+
+ def test_entities_Holden_Glova
+- document = <<-EOL
++ document = <<~EOL
+
+
+diff --git a/test/rexml/test_core.rb b/test/rexml/test_core.rb
+index ee5438d..44e2e7e 100644
+--- a/test/rexml/test_core.rb
++++ b/test/rexml/test_core.rb
+@@ -1,8 +1,6 @@
+-# coding: utf-8
++# -*- coding: utf-8 -*-
+ # frozen_string_literal: false
+
+-require_relative "rexml_test_utils"
+-
+ require "rexml/document"
+ require "rexml/parseexception"
+ require "rexml/output"
+@@ -14,10 +12,10 @@ require_relative "listener"
+
+ module REXMLTests
+ class Tester < Test::Unit::TestCase
+- include REXMLTestUtils
++ include Helper::Fixture
+ include REXML
+ def setup
+- @xsa_source = <<-EOL
++ @xsa_source = <<~EOL
+
+
+
++
++
++
++
++ XML
++ end
++ end
++
++ def test_attribute_default_namespace
++ # https://www.w3.org/TR/xml-names/#uniqAttrs
++ document = Document.new(<<-XML)
++
++
++
++
++
++ XML
++ attributes = document.root.elements.collect do |element|
++ element.attributes.each_attribute.collect do |attribute|
++ [attribute.prefix, attribute.namespace, attribute.name]
++ end
++ end
++ assert_equal([
++ [
++ ["", "", "a"],
++ ["", "", "b"],
++ ],
++ [
++ ["", "", "a"],
++ ["n1", "http://www.w3.org", "a"],
++ ],
++ ],
++ attributes)
++ end
++
+ def test_cdata
+ test = "The quick brown fox jumped
+ & < & < \" '
+@@ -681,7 +728,7 @@ module REXMLTests
+ koln_iso_8859_1 = "K\xF6ln"
+ koln_utf8 = "K\xc3\xb6ln"
+ source = Source.new( koln_iso_8859_1, 'iso-8859-1' )
+- results = source.scan(/.*/)[0]
++ results = source.match(/.*/)[0]
+ koln_utf8.force_encoding('UTF-8') if koln_utf8.respond_to?(:force_encoding)
+ assert_equal koln_utf8, results
+ output << results
+@@ -823,7 +870,7 @@ EOL
+ assert_equal 'two', doc.root.elements[1].namespace
+ assert_equal 'foo', doc.root.namespace
+
+- doc = Document.new <<-EOL
++ doc = Document.new <<~EOL
+
+
+@@ -877,18 +924,18 @@ EOL
+ EOL
+
+ # The most common case. People not caring about the namespaces much.
+- assert_equal( "XY", XPath.match( doc, "/test/a/text()" ).join )
+- assert_equal( "XY", XPath.match( doc, "/test/x:a/text()" ).join )
++ assert_equal( "XY", XPath.match( doc, "/*:test/*:a/text()" ).join )
++ assert_equal( "XY", XPath.match( doc, "/*:test/x:a/text()" ).join )
+ # Surprising? I don't think so, if you believe my definition of the "common case"
+- assert_equal( "XYZ", XPath.match( doc, "//a/text()" ).join )
++ assert_equal( "XYZ", XPath.match( doc, "//*:a/text()" ).join )
+
+ # These are the uncommon cases. Namespaces are actually important, so we define our own
+ # mappings, and pass them in.
+ assert_equal( "XY", XPath.match( doc, "/f:test/f:a/text()", { "f" => "1" } ).join )
+ # The namespaces are defined, and override the original mappings
+- assert_equal( "", XPath.match( doc, "/test/a/text()", { "f" => "1" } ).join )
++ assert_equal( "XY", XPath.match( doc, "/*:test/*:a/text()", { "f" => "1" } ).join )
+ assert_equal( "", XPath.match( doc, "/x:test/x:a/text()", { "f" => "1" } ).join )
+- assert_equal( "", XPath.match( doc, "//a/text()", { "f" => "1" } ).join )
++ assert_equal( "XYZ", XPath.match( doc, "//*:a/text()", { "f" => "1" } ).join )
+ end
+
+ def test_processing_instruction
+@@ -900,7 +947,7 @@ EOL
+ end
+
+ def test_oses_with_bad_EOLs
+- Document.new("\n\n\n\n\n\n\n\n")
++ Document.new("\n\n\n\n\n")
+ end
+
+ # Contributed (with patch to fix bug) by Kouhei
+@@ -927,7 +974,7 @@ EOL
+ end
+
+ def test_hyphens_in_doctype
+- doc = REXML::Document.new <<-EOQ
++ doc = REXML::Document.new <<~EOQ
+
+
+
+@@ -1043,7 +1090,7 @@ EOL
+ def test_text_raw
+ # From the REXML tutorial
+ # (http://www.germane-software.com/software/rexml/test/data/tutorial.html)
+- doc = Document.new <<-EOL
++ doc = Document.new <<~EOL
+
+
+@@ -1277,11 +1324,26 @@ EOL
+ exception = assert_raise(ParseException) do
+ Document.new(src)
+ end
+- assert_equal(<<-DETAIL, exception.to_s)
++ assert_equal(<<-DETAIL.chomp, exception.to_s)
+ Missing attribute value start quote:
+ Line: 1
+ Position: 16
+ Last 80 unconsumed characters:
++value/>
++ DETAIL
++ end
++
++ def test_parse_exception_on_missing_attribute_end_quote
++ src = '
++Line: 1
++Position: 17
++Last 80 unconsumed characters:
++value/>
+ DETAIL
+ end
+
+@@ -1377,7 +1439,7 @@ ENDXML
+ d.root.add_element( "bah" )
+ p=REXML::Formatters::Pretty.new(2)
+ p.compact = true # Don't add whitespace to text nodes unless necessary
+- p.write(d,out="")
++ p.write(d,out=+"")
+ assert_equal( expected, out )
+ end
+
+@@ -1391,8 +1453,8 @@ ENDXML
+
+ def test_ticket_102
+ doc = REXML::Document.new ''
+- assert_equal( "foo", doc.root.elements["item"].attribute("name","ns").to_s )
+- assert_equal( "item", doc.root.elements["item[@name='foo']"].name )
++ assert_equal( "foo", doc.root.elements["*:item"].attribute("name","ns").to_s )
++ assert_equal( "item", doc.root.elements["*:item[@name='foo']"].name )
+ end
+
+ def test_ticket_14
+@@ -1421,11 +1483,11 @@ ENDXML
+ doc = REXML::Document.new(
+ 'text'
+ )
+- assert_equal 'text', doc.text( "/doc/item[@name='foo']" )
++ assert_equal 'text', doc.text( "/*:doc/*:item[@name='foo']" )
+ assert_equal "name='foo'",
+- doc.root.elements["item"].attribute("name", "ns").inspect
++ doc.root.elements["*:item"].attribute("name", "ns").inspect
+ assert_equal "text",
+- doc.root.elements["item[@name='foo']"].to_s
++ doc.root.elements["*:item[@name='foo']"].to_s
+ end
+
+ def test_ticket_135
+@@ -1453,8 +1515,10 @@ ENDXML
+ "" => attribute("version", "1.0"),
+ },
+ }
+- assert_equal(expected, doc.root.attributes)
+- assert_equal(expected, REXML::Document.new(doc.root.to_s).root.attributes)
++ assert_equal(expected,
++ doc.root.attributes.to_h)
++ assert_equal(expected,
++ REXML::Document.new(doc.root.to_s).root.attributes.to_h)
+ end
+
+ def test_empty_doc
+diff --git a/test/rexml/test_doctype.rb b/test/rexml/test_doctype.rb
+index d728cba..b20d30a 100644
+--- a/test/rexml/test_doctype.rb
++++ b/test/rexml/test_doctype.rb
+@@ -1,6 +1,4 @@
+ # frozen_string_literal: false
+-require 'test/unit'
+-require 'rexml/document'
+
+ module REXMLTests
+ class TestDocTypeAccessor < Test::Unit::TestCase
+@@ -41,6 +39,12 @@ module REXMLTests
+ @doc_type_public_system.to_s)
+ end
+
++ def test_to_s_apostrophe
++ @doc_type_public_system.parent.context[:prologue_quote] = :apostrophe
++ assert_equal("",
++ @doc_type_public_system.to_s)
++ end
++
+ def test_system
+ assert_equal([
+ @sysid,
+@@ -82,6 +86,35 @@ module REXMLTests
+ assert_equal("",
+ doctype.to_s)
+ end
++
++ def test_to_s_apostrophe
++ doctype = REXML::DocType.new(["root", "SYSTEM", nil, "root.dtd"])
++ doc = REXML::Document.new
++ doc << doctype
++ doctype.parent.context[:prologue_quote] = :apostrophe
++ assert_equal("",
++ doctype.to_s)
++ end
++
++ def test_to_s_single_quote_apostrophe
++ doctype = REXML::DocType.new(["root", "SYSTEM", nil, "root'.dtd"])
++ doc = REXML::Document.new
++ doc << doctype
++ # This isn't used.
++ doctype.parent.context[:prologue_quote] = :apostrophe
++ assert_equal("",
++ doctype.to_s)
++ end
++
++ def test_to_s_double_quote
++ doctype = REXML::DocType.new(["root", "SYSTEM", nil, "root\".dtd"])
++ doc = REXML::Document.new
++ doc << doctype
++ # This isn't used.
++ doctype.parent.context[:prologue_quote] = :apostrophe
++ assert_equal("",
++ doctype.to_s)
++ end
+ end
+ end
+
+@@ -92,6 +125,25 @@ module REXMLTests
+ assert_equal("",
+ doctype.to_s)
+ end
++
++ def test_to_s_apostrophe
++ doctype = REXML::DocType.new(["root", "PUBLIC", "pub", "root.dtd"])
++ doc = REXML::Document.new
++ doc << doctype
++ doctype.parent.context[:prologue_quote] = :apostrophe
++ assert_equal("",
++ doctype.to_s)
++ end
++
++ def test_to_s_apostrophe_include_apostrophe
++ doctype = REXML::DocType.new(["root", "PUBLIC", "pub'", "root.dtd"])
++ doc = REXML::Document.new
++ doc << doctype
++ # This isn't used.
++ doctype.parent.context[:prologue_quote] = :apostrophe
++ assert_equal("",
++ doctype.to_s)
++ end
+ end
+
+ class TestSystemLiteral < self
+@@ -101,6 +153,25 @@ module REXMLTests
+ doctype.to_s)
+ end
+
++ def test_to_s_apostrophe
++ doctype = REXML::DocType.new(["root", "PUBLIC", "pub", "root.dtd"])
++ doc = REXML::Document.new
++ doc << doctype
++ doctype.parent.context[:prologue_quote] = :apostrophe
++ assert_equal("",
++ doctype.to_s)
++ end
++
++ def test_to_s_apostrophe_include_apostrophe
++ doctype = REXML::DocType.new(["root", "PUBLIC", "pub", "root'.dtd"])
++ doc = REXML::Document.new
++ doc << doctype
++ # This isn't used.
++ doctype.parent.context[:prologue_quote] = :apostrophe
++ assert_equal("",
++ doctype.to_s)
++ end
++
+ def test_to_s_double_quote
+ doctype = REXML::DocType.new(["root", "PUBLIC", "pub", "root\".dtd"])
+ assert_equal("",
+@@ -143,6 +214,62 @@ module REXMLTests
+ decl(@id, "system\"literal").to_s)
+ end
+
++ def test_to_s_apostrophe
++ document = REXML::Document.new(<<-XML)
++
++
++ XML
++ document.context[:prologue_quote] = :apostrophe
++ notation = document.doctype.notations[0]
++ assert_equal("",
++ notation.to_s)
++ end
++
++ def test_to_s_apostrophe_pubid_literal_include_apostrophe
++ document = REXML::Document.new(<<-XML)
++
++
++ XML
++ # This isn't used for PubidLiteral because PubidChar includes '.
++ document.context[:prologue_quote] = :apostrophe
++ notation = document.doctype.notations[0]
++ assert_equal("",
++ notation.to_s)
++ end
++
++ def test_to_s_apostrophe_system_literal_include_apostrophe
++ document = REXML::Document.new(<<-XML)
++
++
++ XML
++ # This isn't used for SystemLiteral because SystemLiteral includes '.
++ document.context[:prologue_quote] = :apostrophe
++ notation = document.doctype.notations[0]
++ assert_equal("",
++ notation.to_s)
++ end
++
++ def test_to_s_apostrophe_system_literal_include_double_quote
++ document = REXML::Document.new(<<-XML)
++
++
++ XML
++ # This isn't used for SystemLiteral because SystemLiteral includes ".
++ # But quoted by ' because SystemLiteral includes ".
++ document.context[:prologue_quote] = :apostrophe
++ notation = document.doctype.notations[0]
++ assert_equal("",
++ notation.to_s)
++ end
++
+ private
+ def decl(id, uri)
+ REXML::NotationDecl.new(@name, "PUBLIC", id, uri)
+@@ -170,6 +297,48 @@ module REXMLTests
+ decl("#{@id}\"").to_s)
+ end
+
++ def test_to_s_apostrophe
++ document = REXML::Document.new(<<-XML)
++
++
++ XML
++ document.context[:prologue_quote] = :apostrophe
++ notation = document.doctype.notations[0]
++ assert_equal("",
++ notation.to_s)
++ end
++
++ def test_to_s_apostrophe_include_apostrophe
++ document = REXML::Document.new(<<-XML)
++
++
++ XML
++ # This isn't used for SystemLiteral because SystemLiteral includes '.
++ document.context[:prologue_quote] = :apostrophe
++ notation = document.doctype.notations[0]
++ assert_equal("",
++ notation.to_s)
++ end
++
++ def test_to_s_apostrophe_include_double_quote
++ document = REXML::Document.new(<<-XML)
++
++
++ XML
++ # This isn't used for SystemLiteral because SystemLiteral includes ".
++ # But quoted by ' because SystemLiteral includes ".
++ document.context[:prologue_quote] = :apostrophe
++ notation = document.doctype.notations[0]
++ assert_equal("",
++ notation.to_s)
++ end
++
+ private
+ def decl(id)
+ REXML::NotationDecl.new(@name, "SYSTEM", nil, id)
+diff --git a/test/rexml/test_document.rb b/test/rexml/test_document.rb
+index c0faae4..2b0a8a7 100644
+--- a/test/rexml/test_document.rb
++++ b/test/rexml/test_document.rb
+@@ -1,13 +1,14 @@
+ # -*- coding: utf-8 -*-
+ # frozen_string_literal: false
+
+-require "rexml/document"
+-require "test/unit"
++require 'core_assertions'
+
+ module REXMLTests
+ class TestDocument < Test::Unit::TestCase
++ include Test::Unit::CoreAssertions
++
+ def test_version_attributes_to_s
+- doc = REXML::Document.new(<<-eoxml)
++ doc = REXML::Document.new(<<~eoxml)
+
+ ")
+- assert_equal( 1, REXML::XPath.match(doc,
+- "//*[local-name()='c' and @id='b']").size )
+- assert_equal( 1, REXML::XPath.match(doc,
+- "//*[ local-name()='c' and @id='b' ]").size )
+- assert_equal( 1, REXML::XPath.match(doc,
+- "//*[ local-name() = 'c' and @id = 'b' ]").size )
+- assert_equal( 1,
+- REXML::XPath.match(doc, '/a/c[@id]').size )
+- assert_equal( 1,
+- REXML::XPath.match(doc, '/a/c[(@id)]').size )
+- assert_equal( 1,
+- REXML::XPath.match(doc, '/a/c[ @id ]').size )
+- assert_equal( 1,
+- REXML::XPath.match(doc, '/a/c[ (@id) ]').size )
+- assert_equal( 1,
+- REXML::XPath.match(doc, '/a/c[( @id )]').size )
+- assert_equal( 1, REXML::XPath.match(doc.root,
+- '/a/c[ ( @id ) ]').size )
+- assert_equal( 1, REXML::XPath.match(doc,
+- '/a/c [ ( @id ) ] ').size )
+- assert_equal( 1, REXML::XPath.match(doc,
+- ' / a / c [ ( @id ) ] ').size )
++ match = lambda do |xpath|
++ REXML::XPath.match(doc, xpath).collect(&:to_s)
++ end
++ assert_equal([""],
++ match.call("//*[local-name()='c' and @id='b']"))
++ assert_equal([""],
++ match.call("//*[ local-name()='c' and @id='b' ]"))
++ assert_equal([""],
++ match.call("//*[ local-name() = 'c' and @id = 'b' ]"))
++ assert_equal(["", ""],
++ match.call('/a/c[@id]'))
++ assert_equal(["", ""],
++ match.call('/a/c[(@id)]'))
++ assert_equal(["", ""],
++ match.call('/a/c[ @id ]'))
++ assert_equal(["", ""],
++ match.call('/a/c[ (@id) ]'))
++ assert_equal(["", ""],
++ match.call('/a/c[( @id )]'))
++ assert_equal(["", ""],
++ match.call('/a/c[ ( @id ) ]'))
++ assert_equal(["", ""],
++ match.call('/a/c [ ( @id ) ] '))
++ assert_equal(["", ""],
++ match.call(' / a / c [ ( @id ) ] '))
++ assert_equal(["", ""],
++ match.call('/ a / child:: c [( @id )] /'))
+ end
+
+ def test_text_nodes
+@@ -692,11 +740,22 @@ module REXMLTests
+ end
+
+ def test_ordering
+- source = ""
++ source = <<-XML
++
++
++
++
++
++
++
++
++
++
++ XML
+ d = REXML::Document.new( source )
+ r = REXML::XPath.match( d, %q{/a/*/*[1]} )
+- assert_equal( 1, r.size )
+- r.each { |el| assert_equal( '1', el.attribute('id').value ) }
++ assert_equal(["1", "3"],
++ r.collect {|element| element.attribute("id").value})
+ end
+
+ def test_descendant_or_self_ordering
+@@ -830,31 +889,44 @@ module REXMLTests
+
+ EOL
+ d = REXML::Document.new( string )
+- c1 = XPath.match( d, '/a/*/*[1]' )
+- assert_equal( 1, c1.length )
+- assert_equal( 'c1', c1[0].name )
++ cs = XPath.match( d, '/a/*/*[1]' )
++ assert_equal(["c1", "c2"], cs.collect(&:name))
+ end
+
+ def test_sum
+- d = Document.new(""+
+- "123"+
+- "12"+
+- ""+
+- "")
+-
+- for v,p in [[6, "sum(/a/b)"],
+- [9, "sum(//b | //d)"],
+- [3, "sum(/a/e/@*)"] ]
+- assert_equal( v, XPath::match( d, p ).first )
+- end
++ d = Document.new(<<-XML)
++
++ 1
++ 2
++ 3
++
++ 1
++ 2
++
++
++
++
++ XML
++
++ assert_equal([6], XPath::match(d, "sum(/a/b)"))
++ assert_equal([9], XPath::match(d, "sum(//b | //d)"))
++ assert_equal([3], XPath::match(d, "sum(/a/e/@*)"))
+ end
+
+ def test_xpath_namespace
+- d = REXML::Document.new("xa")
+- x = d.root
+- num = 0
+- x.each_element('tada') { num += 1 }
+- assert_equal(1, num)
++ d = REXML::Document.new(<<-XML)
++
++
++ xa
++ xb
++
++ XML
++ actual = []
++ d.root.each_element('tada') do |element|
++ actual << element.to_s
++ end
++ assert_equal(["xa", "xb"],
++ actual)
+ end
+
+ def test_ticket_39
+@@ -990,7 +1062,7 @@ EOF
+ "
+ d = Document.new(data)
+ res = d.elements.to_a( "//c" ).collect {|e| e.attributes['id'].to_i}
+- assert_equal( res, res.sort )
++ assert_equal((1..12).to_a, res)
+ end
+
+ def ticket_61_fixture(doc, xpath)
+diff --git a/test/rexml/xpath/test_compare.rb b/test/rexml/xpath/test_compare.rb
+new file mode 100644
+index 0000000..11d11e5
+--- /dev/null
++++ b/test/rexml/xpath/test_compare.rb
+@@ -0,0 +1,252 @@
++# frozen_string_literal: false
++
++module REXMLTests
++ class TestXPathCompare < Test::Unit::TestCase
++ def match(xml, xpath)
++ document = REXML::Document.new(xml)
++ REXML::XPath.match(document, xpath)
++ end
++
++ class TestEqual < self
++ class TestNodeSet < self
++ def test_boolean_true
++ xml = <<-XML
++
++
++
++
++
++ XML
++ assert_equal([true],
++ match(xml, "/root/child=true()"))
++ end
++
++ def test_boolean_false
++ xml = <<-XML
++
++
++
++ XML
++ assert_equal([false],
++ match(xml, "/root/child=true()"))
++ end
++
++ def test_number_true
++ xml = <<-XML
++
++
++ 100
++ 200
++
++ XML
++ assert_equal([true],
++ match(xml, "/root/child=100"))
++ end
++
++ def test_number_false
++ xml = <<-XML
++
++
++ 100
++ 200
++
++ XML
++ assert_equal([false],
++ match(xml, "/root/child=300"))
++ end
++
++ def test_string_true
++ xml = <<-XML
++
++
++ text
++ string
++
++ XML
++ assert_equal([true],
++ match(xml, "/root/child='string'"))
++ end
++
++ def test_string_false
++ xml = <<-XML
++
++
++ text
++ string
++
++ XML
++ assert_equal([false],
++ match(xml, "/root/child='nonexistent'"))
++ end
++ end
++
++ class TestBoolean < self
++ def test_number_true
++ xml = ""
++ assert_equal([true],
++ match(xml, "true()=1"))
++ end
++
++ def test_number_false
++ xml = ""
++ assert_equal([false],
++ match(xml, "true()=0"))
++ end
++
++ def test_string_true
++ xml = ""
++ assert_equal([true],
++ match(xml, "true()='string'"))
++ end
++
++ def test_string_false
++ xml = ""
++ assert_equal([false],
++ match(xml, "true()=''"))
++ end
++ end
++
++ class TestNumber < self
++ def test_string_true
++ xml = ""
++ assert_equal([true],
++ match(xml, "1='1'"))
++ end
++
++ def test_string_false
++ xml = ""
++ assert_equal([false],
++ match(xml, "1='2'"))
++ end
++ end
++ end
++
++ class TestGreaterThan < self
++ class TestNodeSet < self
++ def test_boolean_truex
++ xml = <<-XML
++
++
++
++
++ XML
++ assert_equal([true],
++ match(xml, "/root/child>false()"))
++ end
++
++ def test_boolean_false
++ xml = <<-XML
++
++
++
++
++ XML
++ assert_equal([false],
++ match(xml, "/root/child>true()"))
++ end
++
++ def test_number_true
++ xml = <<-XML
++
++
++ 100
++ 200
++
++ XML
++ assert_equal([true],
++ match(xml, "/root/child>199"))
++ end
++
++ def test_number_false
++ xml = <<-XML
++
++
++ 100
++ 200
++
++ XML
++ assert_equal([false],
++ match(xml, "/root/child>200"))
++ end
++
++ def test_string_true
++ xml = <<-XML
++
++
++ 100
++ 200
++
++ XML
++ assert_equal([true],
++ match(xml, "/root/child>'199'"))
++ end
++
++ def test_string_false
++ xml = <<-XML
++
++
++ 100
++ 200
++
++ XML
++ assert_equal([false],
++ match(xml, "/root/child>'200'"))
++ end
++ end
++
++ class TestBoolean < self
++ def test_string_true
++ xml = ""
++ assert_equal([true],
++ match(xml, "true()>'0'"))
++ end
++
++ def test_string_false
++ xml = ""
++ assert_equal([false],
++ match(xml, "true()>'1'"))
++ end
++ end
++
++ class TestNumber < self
++ def test_boolean_true
++ xml = ""
++ assert_equal([true],
++ match(xml, "true()>0"))
++ end
++
++ def test_number_false
++ xml = ""
++ assert_equal([false],
++ match(xml, "true()>1"))
++ end
++
++ def test_string_true
++ xml = ""
++ assert_equal([true],
++ match(xml, "1>'0'"))
++ end
++
++ def test_string_false
++ xml = ""
++ assert_equal([false],
++ match(xml, "1>'1'"))
++ end
++ end
++
++ class TestString < self
++ def test_string_true
++ xml = ""
++ assert_equal([true],
++ match(xml, "'1'>'0'"))
++ end
++
++ def test_string_false
++ xml = ""
++ assert_equal([false],
++ match(xml, "'1'>'1'"))
++ end
++ end
++ end
++ end
++end
+diff --git a/test/rexml/xpath/test_node.rb b/test/rexml/xpath/test_node.rb
+index e0e958e..742bfbb 100644
+--- a/test/rexml/xpath/test_node.rb
++++ b/test/rexml/xpath/test_node.rb
+@@ -1,10 +1,6 @@
+ # -*- coding: utf-8 -*-
+ # frozen_string_literal: false
+
+-require_relative "../rexml_test_utils"
+-
+-require "rexml/document"
+-
+ module REXMLTests
+ class TestXPathNode < Test::Unit::TestCase
+ def matches(xml, xpath)
+diff --git a/test/rexml/xpath/test_predicate.rb b/test/rexml/xpath/test_predicate.rb
+index ce1aaa3..278e376 100644
+--- a/test/rexml/xpath/test_predicate.rb
++++ b/test/rexml/xpath/test_predicate.rb
+@@ -1,13 +1,12 @@
+ # frozen_string_literal: false
+-require "test/unit/testcase"
+-require "rexml/document"
++
+ require "rexml/xpath"
+ require "rexml/parsers/xpathparser"
+
+ module REXMLTests
+ class TestXPathPredicate < Test::Unit::TestCase
+ include REXML
+- SRC=<<-EOL
++ SRC=<<~EOL
+
+
+ free flowing text.
+@@ -29,6 +28,15 @@ module REXMLTests
+
+ end
+
++ def test_predicate_only
++ error = assert_raise(REXML::ParseException) do
++ do_path("[article]")
++ end
++ assert_equal("Garbage component exists at the end: " +
++ "<[article]>: <[article]>",
++ error.message)
++ end
++
+ def test_predicates_parent
+ path = '//section[../self::section[@role="division"]]'
+ m = do_path( path )
+diff --git a/test/rexml/xpath/test_text.rb b/test/rexml/xpath/test_text.rb
+index 7222388..dccc4c8 100644
+--- a/test/rexml/xpath/test_text.rb
++++ b/test/rexml/xpath/test_text.rb
+@@ -1,6 +1,5 @@
+ # frozen_string_literal: false
+-require 'test/unit'
+-require 'rexml/document'
++
+ require 'rexml/element'
+ require 'rexml/xpath'
+
+--
+2.27.0
+
diff --git a/upgrade-lib-rexml-to-3.3.1.patch b/upgrade-lib-rexml-to-3.3.1.patch
new file mode 100644
index 0000000..2691475
--- /dev/null
+++ b/upgrade-lib-rexml-to-3.3.1.patch
@@ -0,0 +1,7494 @@
+From 20017eea807e8fa386aa5c79ae779004d8b366dd Mon Sep 17 00:00:00 2001
+From: Sutou Kouhei
+Date: Tue, 25 Jun 2024 11:26:33 +0900
+Subject: [PATCH] Add 3.3.1 entry
+
+Backport from https://github.com/ruby/rexml/tree/v3.3.1/lib/rexml
+
+---
+ lib/rexml/attlistdecl.rb | 4 +-
+ lib/rexml/attribute.rb | 54 +-
+ lib/rexml/cdata.rb | 2 +-
+ lib/rexml/child.rb | 2 +-
+ lib/rexml/comment.rb | 2 +-
+ lib/rexml/doctype.rb | 49 +-
+ lib/rexml/document.rb | 256 ++-
+ lib/rexml/dtd/attlistdecl.rb | 2 +-
+ lib/rexml/dtd/dtd.rb | 12 +-
+ lib/rexml/dtd/elementdecl.rb | 2 +-
+ lib/rexml/dtd/entitydecl.rb | 2 +-
+ lib/rexml/dtd/notationdecl.rb | 2 +-
+ lib/rexml/element.rb | 2297 +++++++++++++++++-----
+ lib/rexml/entity.rb | 48 +-
+ lib/rexml/formatters/default.rb | 12 +-
+ lib/rexml/formatters/pretty.rb | 6 +-
+ lib/rexml/formatters/transitive.rb | 2 +-
+ lib/rexml/functions.rb | 105 +-
+ lib/rexml/instruction.rb | 32 +-
+ lib/rexml/light/node.rb | 12 +-
+ lib/rexml/namespace.rb | 29 +-
+ lib/rexml/node.rb | 18 +-
+ lib/rexml/output.rb | 2 +-
+ lib/rexml/parent.rb | 2 +-
+ lib/rexml/parseexception.rb | 1 +
+ lib/rexml/parsers/baseparser.rb | 551 +++---
+ lib/rexml/parsers/lightparser.rb | 6 +-
+ lib/rexml/parsers/pullparser.rb | 6 +-
+ lib/rexml/parsers/sax2parser.rb | 8 +-
+ lib/rexml/parsers/streamparser.rb | 2 +-
+ lib/rexml/parsers/treeparser.rb | 27 +-
+ lib/rexml/parsers/ultralightparser.rb | 4 +-
+ lib/rexml/parsers/xpathparser.rb | 332 ++--
+ lib/rexml/quickpath.rb | 4 +-
+ lib/rexml/rexml.rb | 55 +-
+ lib/rexml/source.rb | 223 ++-
+ lib/rexml/syncenumerator.rb | 33 -
+ lib/rexml/text.rb | 76 +-
+ lib/rexml/undefinednamespaceexception.rb | 2 +-
+ lib/rexml/validation/relaxng.rb | 4 +-
+ lib/rexml/validation/validation.rb | 2 +-
+ lib/rexml/xmldecl.rb | 40 +-
+ lib/rexml/xpath.rb | 16 +-
+ lib/rexml/xpath_parser.rb | 1052 ++++++----
+ 44 files changed, 3677 insertions(+), 1721 deletions(-)
+ delete mode 100644 lib/rexml/syncenumerator.rb
+
+diff --git a/lib/rexml/attlistdecl.rb b/lib/rexml/attlistdecl.rb
+index dc1d2ad..44a91d6 100644
+--- a/lib/rexml/attlistdecl.rb
++++ b/lib/rexml/attlistdecl.rb
+@@ -1,7 +1,7 @@
+ # frozen_string_literal: false
+ #vim:ts=2 sw=2 noexpandtab:
+-require 'rexml/child'
+-require 'rexml/source'
++require_relative 'child'
++require_relative 'source'
+
+ module REXML
+ # This class needs:
+diff --git a/lib/rexml/attribute.rb b/lib/rexml/attribute.rb
+index ca5984e..11893a9 100644
+--- a/lib/rexml/attribute.rb
++++ b/lib/rexml/attribute.rb
+@@ -1,6 +1,6 @@
+-# frozen_string_literal: false
+-require "rexml/namespace"
+-require 'rexml/text'
++# frozen_string_literal: true
++require_relative "namespace"
++require_relative 'text'
+
+ module REXML
+ # Defines an Element Attribute; IE, a attribute=value pair, as in:
+@@ -13,9 +13,6 @@ module REXML
+
+ # The element to which this attribute belongs
+ attr_reader :element
+- # The normalized value of this attribute. That is, the attribute with
+- # entities intact.
+- attr_writer :normalized
+ PATTERN = /\s*(#{NAME_STR})\s*=\s*(["'])(.*?)\2/um
+
+ NEEDS_A_SECOND_CHECK = /(<|&((#{Entity::NAME});|(#0*((?:\d+)|(?:x[a-fA-F0-9]+)));)?)/um
+@@ -67,15 +64,11 @@ module REXML
+ # e.add_attribute( "nsa:a", "aval" )
+ # e.add_attribute( "b", "bval" )
+ # e.attributes.get_attribute( "a" ).prefix # -> "nsa"
+- # e.attributes.get_attribute( "b" ).prefix # -> "elns"
++ # e.attributes.get_attribute( "b" ).prefix # -> ""
+ # a = Attribute.new( "x", "y" )
+ # a.prefix # -> ""
+ def prefix
+- pf = super
+- if pf == ""
+- pf = @element.prefix if @element
+- end
+- pf
++ super
+ end
+
+ # Returns the namespace URL, if defined, or nil otherwise
+@@ -86,9 +79,26 @@ module REXML
+ # e.add_attribute("nsx:a", "c")
+ # e.attribute("ns:a").namespace # => "http://url"
+ # e.attribute("nsx:a").namespace # => nil
++ #
++ # This method always returns "" for no namespace attribute. Because
++ # the default namespace doesn't apply to attribute names.
++ #
++ # From https://www.w3.org/TR/xml-names/#uniqAttrs
++ #
++ # > the default namespace does not apply to attribute names
++ #
++ # e = REXML::Element.new("el")
++ # e.add_namespace("", "http://example.com/")
++ # e.namespace # => "http://example.com/"
++ # e.add_attribute("a", "b")
++ # e.attribute("a").namespace # => ""
+ def namespace arg=nil
+ arg = prefix if arg.nil?
+- @element.namespace arg
++ if arg == ""
++ ""
++ else
++ @element.namespace(arg)
++ end
+ end
+
+ # Returns true if other is an Attribute and has the same name and value,
+@@ -109,10 +119,13 @@ module REXML
+ # b = Attribute.new( "ns:x", "y" )
+ # b.to_string # -> "ns:x='y'"
+ def to_string
++ value = to_s
+ if @element and @element.context and @element.context[:attribute_quote] == :quote
+- %Q^#@expanded_name="#{to_s().gsub(/"/, '"')}"^
++ value = value.gsub('"', '"') if value.include?('"')
++ %Q^#@expanded_name="#{value}"^
+ else
+- "#@expanded_name='#{to_s().gsub(/'/, ''')}'"
++ value = value.gsub("'", ''') if value.include?("'")
++ "#@expanded_name='#{value}'"
+ end
+ end
+
+@@ -128,7 +141,6 @@ module REXML
+ return @normalized if @normalized
+
+ @normalized = Text::normalize( @unnormalized, doctype )
+- @unnormalized = nil
+ @normalized
+ end
+
+@@ -137,10 +149,16 @@ module REXML
+ def value
+ return @unnormalized if @unnormalized
+ @unnormalized = Text::unnormalize( @normalized, doctype )
+- @normalized = nil
+ @unnormalized
+ end
+
++ # The normalized value of this attribute. That is, the attribute with
++ # entities intact.
++ def normalized=(new_normalized)
++ @normalized = new_normalized
++ @unnormalized = nil
++ end
++
+ # Returns a copy of this attribute
+ def clone
+ Attribute.new self
+@@ -177,7 +195,7 @@ module REXML
+ end
+
+ def inspect
+- rv = ""
++ rv = +""
+ write( rv )
+ rv
+ end
+diff --git a/lib/rexml/cdata.rb b/lib/rexml/cdata.rb
+index 2238446..997f5a0 100644
+--- a/lib/rexml/cdata.rb
++++ b/lib/rexml/cdata.rb
+@@ -1,5 +1,5 @@
+ # frozen_string_literal: false
+-require "rexml/text"
++require_relative "text"
+
+ module REXML
+ class CData < Text
+diff --git a/lib/rexml/child.rb b/lib/rexml/child.rb
+index d23451e..cc6e9a4 100644
+--- a/lib/rexml/child.rb
++++ b/lib/rexml/child.rb
+@@ -1,5 +1,5 @@
+ # frozen_string_literal: false
+-require "rexml/node"
++require_relative "node"
+
+ module REXML
+ ##
+diff --git a/lib/rexml/comment.rb b/lib/rexml/comment.rb
+index 822fe0d..52c58b4 100644
+--- a/lib/rexml/comment.rb
++++ b/lib/rexml/comment.rb
+@@ -1,5 +1,5 @@
+ # frozen_string_literal: false
+-require "rexml/child"
++require_relative "child"
+
+ module REXML
+ ##
+diff --git a/lib/rexml/doctype.rb b/lib/rexml/doctype.rb
+index cb9bf57..f359048 100644
+--- a/lib/rexml/doctype.rb
++++ b/lib/rexml/doctype.rb
+@@ -1,20 +1,25 @@
+ # frozen_string_literal: false
+-require "rexml/parent"
+-require "rexml/parseexception"
+-require "rexml/namespace"
+-require 'rexml/entity'
+-require 'rexml/attlistdecl'
+-require 'rexml/xmltokens'
++require_relative "parent"
++require_relative "parseexception"
++require_relative "namespace"
++require_relative 'entity'
++require_relative 'attlistdecl'
++require_relative 'xmltokens'
+
+ module REXML
+ class ReferenceWriter
+ def initialize(id_type,
+ public_id_literal,
+- system_literal)
++ system_literal,
++ context=nil)
+ @id_type = id_type
+ @public_id_literal = public_id_literal
+ @system_literal = system_literal
+- @default_quote = "\""
++ if context and context[:prologue_quote] == :apostrophe
++ @default_quote = "'"
++ else
++ @default_quote = "\""
++ end
+ end
+
+ def write(output)
+@@ -150,7 +155,8 @@ module REXML
+ if @external_id
+ reference_writer = ReferenceWriter.new(@external_id,
+ @long_name,
+- @uri)
++ @uri,
++ context)
+ reference_writer.write(output)
+ end
+ unless @children.empty?
+@@ -165,7 +171,11 @@ module REXML
+ end
+
+ def context
+- @parent.context
++ if @parent
++ @parent.context
++ else
++ nil
++ end
+ end
+
+ def entity( name )
+@@ -187,7 +197,7 @@ module REXML
+ when "SYSTEM"
+ nil
+ when "PUBLIC"
+- strip_quotes(@long_name)
++ @long_name
+ end
+ end
+
+@@ -197,9 +207,9 @@ module REXML
+ def system
+ case @external_id
+ when "SYSTEM"
+- strip_quotes(@long_name)
++ @long_name
+ when "PUBLIC"
+- @uri.kind_of?(String) ? strip_quotes(@uri) : nil
++ @uri.kind_of?(String) ? @uri : nil
+ end
+ end
+
+@@ -221,15 +231,6 @@ module REXML
+ notation_decl.name == name
+ }
+ end
+-
+- private
+-
+- # Method contributed by Henrik Martensson
+- def strip_quotes(quoted_string)
+- quoted_string =~ /^[\'\"].*[\'\"]$/ ?
+- quoted_string[1, quoted_string.length-2] :
+- quoted_string
+- end
+ end
+
+ # We don't really handle any of these since we're not a validating
+@@ -287,8 +288,10 @@ module REXML
+ end
+
+ def to_s
++ context = nil
++ context = parent.context if parent
+ notation = ""
+ notation
+diff --git a/lib/rexml/document.rb b/lib/rexml/document.rb
+index 806bc49..b1caa02 100644
+--- a/lib/rexml/document.rb
++++ b/lib/rexml/document.rb
+@@ -1,38 +1,94 @@
+ # frozen_string_literal: false
+-require "rexml/security"
+-require "rexml/element"
+-require "rexml/xmldecl"
+-require "rexml/source"
+-require "rexml/comment"
+-require "rexml/doctype"
+-require "rexml/instruction"
+-require "rexml/rexml"
+-require "rexml/parseexception"
+-require "rexml/output"
+-require "rexml/parsers/baseparser"
+-require "rexml/parsers/streamparser"
+-require "rexml/parsers/treeparser"
++require_relative "security"
++require_relative "element"
++require_relative "xmldecl"
++require_relative "source"
++require_relative "comment"
++require_relative "doctype"
++require_relative "instruction"
++require_relative "rexml"
++require_relative "parseexception"
++require_relative "output"
++require_relative "parsers/baseparser"
++require_relative "parsers/streamparser"
++require_relative "parsers/treeparser"
+
+ module REXML
+- # Represents a full XML document, including PIs, a doctype, etc. A
+- # Document has a single child that can be accessed by root().
+- # Note that if you want to have an XML declaration written for a document
+- # you create, you must add one; REXML documents do not write a default
+- # declaration for you. See |DECLARATION| and |write|.
++ # Represents an XML document.
++ #
++ # A document may have:
++ #
++ # - A single child that may be accessed via method #root.
++ # - An XML declaration.
++ # - A document type.
++ # - Processing instructions.
++ #
++ # == In a Hurry?
++ #
++ # If you're somewhat familiar with XML
++ # and have a particular task in mind,
++ # you may want to see the
++ # {tasks pages}[../doc/rexml/tasks/tocs/master_toc_rdoc.html],
++ # and in particular, the
++ # {tasks page for documents}[../doc/rexml/tasks/tocs/document_toc_rdoc.html].
++ #
+ class Document < Element
+- # A convenient default XML declaration. If you want an XML declaration,
+- # the easiest way to add one is mydoc << Document::DECLARATION
+- # +DEPRECATED+
+- # Use: mydoc << XMLDecl.default
++ # A convenient default XML declaration. Use:
++ #
++ # mydoc << XMLDecl.default
++ #
+ DECLARATION = XMLDecl.default
+
+- # Constructor
+- # @param source if supplied, must be a Document, String, or IO.
+- # Documents have their context and Element attributes cloned.
+- # Strings are expected to be valid XML documents. IOs are expected
+- # to be sources of valid XML documents.
+- # @param context if supplied, contains the context of the document;
+- # this should be a Hash.
++ # :call-seq:
++ # new(string = nil, context = {}) -> new_document
++ # new(io_stream = nil, context = {}) -> new_document
++ # new(document = nil, context = {}) -> new_document
++ #
++ # Returns a new \REXML::Document object.
++ #
++ # When no arguments are given,
++ # returns an empty document:
++ #
++ # d = REXML::Document.new
++ # d.to_s # => ""
++ #
++ # When argument +string+ is given, it must be a string
++ # containing a valid XML document:
++ #
++ # xml_string = 'FooBar'
++ # d = REXML::Document.new(xml_string)
++ # d.to_s # => "FooBar"
++ #
++ # When argument +io_stream+ is given, it must be an \IO object
++ # that is opened for reading, and when read must return a valid XML document:
++ #
++ # File.write('t.xml', xml_string)
++ # d = File.open('t.xml', 'r') do |io|
++ # REXML::Document.new(io)
++ # end
++ # d.to_s # => "FooBar"
++ #
++ # When argument +document+ is given, it must be an existing
++ # document object, whose context and attributes (but not children)
++ # are cloned into the new document:
++ #
++ # d = REXML::Document.new(xml_string)
++ # d.children # => [ ... >]
++ # d.context = {raw: :all, compress_whitespace: :all}
++ # d.add_attributes({'bar' => 0, 'baz' => 1})
++ # d1 = REXML::Document.new(d)
++ # d1.children # => []
++ # d1.context # => {:raw=>:all, :compress_whitespace=>:all}
++ # d1.attributes # => {"bar"=>bar='0', "baz"=>baz='1'}
++ #
++ # When argument +context+ is given, it must be a hash
++ # containing context entries for the document;
++ # see {Element Context}[../doc/rexml/context_rdoc.html]:
++ #
++ # context = {raw: :all, compress_whitespace: :all}
++ # d = REXML::Document.new(xml_string, context)
++ # d.context # => {:raw=>:all, :compress_whitespace=>:all}
++ #
+ def initialize( source = nil, context = {} )
+ @entity_expansion_count = 0
+ super()
+@@ -46,26 +102,71 @@ module REXML
+ end
+ end
+
++ # :call-seq:
++ # node_type -> :document
++ #
++ # Returns the symbol +:document+.
++ #
+ def node_type
+ :document
+ end
+
+- # Should be obvious
++ # :call-seq:
++ # clone -> new_document
++ #
++ # Returns the new document resulting from executing
++ # Document.new(self). See Document.new.
++ #
+ def clone
+ Document.new self
+ end
+
+- # According to the XML spec, a root node has no expanded name
++ # :call-seq:
++ # expanded_name -> empty_string
++ #
++ # Returns an empty string.
++ #
+ def expanded_name
+ ''
+ #d = doc_type
+ #d ? d.name : "UNDEFINED"
+ end
+-
+ alias :name :expanded_name
+
+- # We override this, because XMLDecls and DocTypes must go at the start
+- # of the document
++ # :call-seq:
++ # add(xml_decl) -> self
++ # add(doc_type) -> self
++ # add(object) -> self
++ #
++ # Adds an object to the document; returns +self+.
++ #
++ # When argument +xml_decl+ is given,
++ # it must be an REXML::XMLDecl object,
++ # which becomes the XML declaration for the document,
++ # replacing the previous XML declaration if any:
++ #
++ # d = REXML::Document.new
++ # d.xml_decl.to_s # => ""
++ # d.add(REXML::XMLDecl.new('2.0'))
++ # d.xml_decl.to_s # => ""
++ #
++ # When argument +doc_type+ is given,
++ # it must be an REXML::DocType object,
++ # which becomes the document type for the document,
++ # replacing the previous document type, if any:
++ #
++ # d = REXML::Document.new
++ # d.doctype.to_s # => ""
++ # d.add(REXML::DocType.new('foo'))
++ # d.doctype.to_s # => ""
++ #
++ # When argument +object+ (not an REXML::XMLDecl or REXML::DocType object)
++ # is given it is added as the last child:
++ #
++ # d = REXML::Document.new
++ # d.add(REXML::Element.new('foo'))
++ # d.to_s # => ""
++ #
+ def add( child )
+ if child.kind_of? XMLDecl
+ if @children[0].kind_of? XMLDecl
+@@ -99,49 +200,108 @@ module REXML
+ end
+ alias :<< :add
+
++ # :call-seq:
++ # add_element(name_or_element = nil, attributes = nil) -> new_element
++ #
++ # Adds an element to the document by calling REXML::Element.add_element:
++ #
++ # REXML::Element.add_element(name_or_element, attributes)
+ def add_element(arg=nil, arg2=nil)
+ rv = super
+ raise "attempted adding second root element to document" if @elements.size > 1
+ rv
+ end
+
+- # @return the root Element of the document, or nil if this document
+- # has no children.
++ # :call-seq:
++ # root -> root_element or nil
++ #
++ # Returns the root element of the document, if it exists, otherwise +nil+:
++ #
++ # d = REXML::Document.new('')
++ # d.root # =>
++ # d = REXML::Document.new('')
++ # d.root # => nil
++ #
+ def root
+ elements[1]
+ #self
+ #@children.find { |item| item.kind_of? Element }
+ end
+
+- # @return the DocType child of the document, if one exists,
+- # and nil otherwise.
++ # :call-seq:
++ # doctype -> doc_type or nil
++ #
++ # Returns the DocType object for the document, if it exists, otherwise +nil+:
++ #
++ # d = REXML::Document.new('')
++ # d.doctype.class # => REXML::DocType
++ # d = REXML::Document.new('')
++ # d.doctype.class # => nil
++ #
+ def doctype
+ @children.find { |item| item.kind_of? DocType }
+ end
+
+- # @return the XMLDecl of this document; if no XMLDecl has been
+- # set, the default declaration is returned.
++ # :call-seq:
++ # xml_decl -> xml_decl
++ #
++ # Returns the XMLDecl object for the document, if it exists,
++ # otherwise the default XMLDecl object:
++ #
++ # d = REXML::Document.new('')
++ # d.xml_decl.class # => REXML::XMLDecl
++ # d.xml_decl.to_s # => ""
++ # d = REXML::Document.new('')
++ # d.xml_decl.class # => REXML::XMLDecl
++ # d.xml_decl.to_s # => ""
++ #
+ def xml_decl
+ rv = @children[0]
+ return rv if rv.kind_of? XMLDecl
+ @children.unshift(XMLDecl.default)[0]
+ end
+
+- # @return the XMLDecl version of this document as a String.
+- # If no XMLDecl has been set, returns the default version.
++ # :call-seq:
++ # version -> version_string
++ #
++ # Returns the XMLDecl version of this document as a string,
++ # if it has been set, otherwise the default version:
++ #
++ # d = REXML::Document.new('')
++ # d.version # => "2.0"
++ # d = REXML::Document.new('')
++ # d.version # => "1.0"
++ #
+ def version
+ xml_decl().version
+ end
+
+- # @return the XMLDecl encoding of this document as an
+- # Encoding object.
+- # If no XMLDecl has been set, returns the default encoding.
++ # :call-seq:
++ # encoding -> encoding_string
++ #
++ # Returns the XMLDecl encoding of the document,
++ # if it has been set, otherwise the default encoding:
++ #
++ # d = REXML::Document.new('')
++ # d.encoding # => "UTF-16"
++ # d = REXML::Document.new('')
++ # d.encoding # => "UTF-8"
++ #
+ def encoding
+ xml_decl().encoding
+ end
+
+- # @return the XMLDecl standalone value of this document as a String.
+- # If no XMLDecl has been set, returns the default setting.
++ # :call-seq:
++ # stand_alone?
++ #
++ # Returns the XMLDecl standalone value of the document as a string,
++ # if it has been set, otherwise the default standalone value:
++ #
++ # d = REXML::Document.new('')
++ # d.stand_alone? # => "yes"
++ # d = REXML::Document.new('')
++ # d.stand_alone? # => nil
++ #
+ def stand_alone?
+ xml_decl().stand_alone?
+ end
+@@ -226,7 +386,7 @@ module REXML
+ end
+ formatter = if indent > -1
+ if transitive
+- require "rexml/formatters/transitive"
++ require_relative "formatters/transitive"
+ REXML::Formatters::Transitive.new( indent, ie_hack )
+ else
+ REXML::Formatters::Pretty.new( indent, ie_hack )
+diff --git a/lib/rexml/dtd/attlistdecl.rb b/lib/rexml/dtd/attlistdecl.rb
+index 32847da..1326cb2 100644
+--- a/lib/rexml/dtd/attlistdecl.rb
++++ b/lib/rexml/dtd/attlistdecl.rb
+@@ -1,5 +1,5 @@
+ # frozen_string_literal: false
+-require "rexml/child"
++require_relative "../child"
+ module REXML
+ module DTD
+ class AttlistDecl < Child
+diff --git a/lib/rexml/dtd/dtd.rb b/lib/rexml/dtd/dtd.rb
+index 927d5d8..8b0f2d7 100644
+--- a/lib/rexml/dtd/dtd.rb
++++ b/lib/rexml/dtd/dtd.rb
+@@ -1,10 +1,10 @@
+ # frozen_string_literal: false
+-require "rexml/dtd/elementdecl"
+-require "rexml/dtd/entitydecl"
+-require "rexml/comment"
+-require "rexml/dtd/notationdecl"
+-require "rexml/dtd/attlistdecl"
+-require "rexml/parent"
++require_relative "elementdecl"
++require_relative "entitydecl"
++require_relative "../comment"
++require_relative "notationdecl"
++require_relative "attlistdecl"
++require_relative "../parent"
+
+ module REXML
+ module DTD
+diff --git a/lib/rexml/dtd/elementdecl.rb b/lib/rexml/dtd/elementdecl.rb
+index 119fd41..20ed023 100644
+--- a/lib/rexml/dtd/elementdecl.rb
++++ b/lib/rexml/dtd/elementdecl.rb
+@@ -1,5 +1,5 @@
+ # frozen_string_literal: false
+-require "rexml/child"
++require_relative "../child"
+ module REXML
+ module DTD
+ class ElementDecl < Child
+diff --git a/lib/rexml/dtd/entitydecl.rb b/lib/rexml/dtd/entitydecl.rb
+index 45707e2..312df65 100644
+--- a/lib/rexml/dtd/entitydecl.rb
++++ b/lib/rexml/dtd/entitydecl.rb
+@@ -1,5 +1,5 @@
+ # frozen_string_literal: false
+-require "rexml/child"
++require_relative "../child"
+ module REXML
+ module DTD
+ class EntityDecl < Child
+diff --git a/lib/rexml/dtd/notationdecl.rb b/lib/rexml/dtd/notationdecl.rb
+index cfdf0b9..04a9b08 100644
+--- a/lib/rexml/dtd/notationdecl.rb
++++ b/lib/rexml/dtd/notationdecl.rb
+@@ -1,5 +1,5 @@
+ # frozen_string_literal: false
+-require "rexml/child"
++require_relative "../child"
+ module REXML
+ module DTD
+ class NotationDecl < Child
+diff --git a/lib/rexml/element.rb b/lib/rexml/element.rb
+index ac9b108..a5808d7 100644
+--- a/lib/rexml/element.rb
++++ b/lib/rexml/element.rb
+@@ -1,23 +1,273 @@
+ # frozen_string_literal: false
+-require "rexml/parent"
+-require "rexml/namespace"
+-require "rexml/attribute"
+-require "rexml/cdata"
+-require "rexml/xpath"
+-require "rexml/parseexception"
++require_relative "parent"
++require_relative "namespace"
++require_relative "attribute"
++require_relative "cdata"
++require_relative "xpath"
++require_relative "parseexception"
+
+ module REXML
+- # An implementation note about namespaces:
+- # As we parse, when we find namespaces we put them in a hash and assign
+- # them a unique ID. We then convert the namespace prefix for the node
+- # to the unique ID. This makes namespace lookup much faster for the
+- # cost of extra memory use. We save the namespace prefix for the
+- # context node and convert it back when we write it.
+- @@namespaces = {}
+-
+- # Represents a tagged XML element. Elements are characterized by
+- # having children, attributes, and names, and can themselves be
+- # children.
++ # An \REXML::Element object represents an XML element.
++ #
++ # An element:
++ #
++ # - Has a name (string).
++ # - May have a parent (another element).
++ # - Has zero or more children
++ # (other elements, text, CDATA, processing instructions, and comments).
++ # - Has zero or more siblings
++ # (other elements, text, CDATA, processing instructions, and comments).
++ # - Has zero or more named attributes.
++ #
++ # == In a Hurry?
++ #
++ # If you're somewhat familiar with XML
++ # and have a particular task in mind,
++ # you may want to see the
++ # {tasks pages}[../doc/rexml/tasks/tocs/master_toc_rdoc.html],
++ # and in particular, the
++ # {tasks page for elements}[../doc/rexml/tasks/tocs/element_toc_rdoc.html].
++ #
++ # === Name
++ #
++ # An element has a name, which is initially set when the element is created:
++ #
++ # e = REXML::Element.new('foo')
++ # e.name # => "foo"
++ #
++ # The name may be changed:
++ #
++ # e.name = 'bar'
++ # e.name # => "bar"
++ #
++ #
++ # === \Parent
++ #
++ # An element may have a parent.
++ #
++ # Its parent may be assigned explicitly when the element is created:
++ #
++ # e0 = REXML::Element.new('foo')
++ # e1 = REXML::Element.new('bar', e0)
++ # e1.parent # => ... >
++ #
++ # Note: the representation of an element always shows the element's name.
++ # If the element has children, the representation indicates that
++ # by including an ellipsis (...).
++ #
++ # The parent may be assigned explicitly at any time:
++ #
++ # e2 = REXML::Element.new('baz')
++ # e1.parent = e2
++ # e1.parent # =>
++ #
++ # When an element is added as a child, its parent is set automatically:
++ #
++ # e1.add_element(e0)
++ # e0.parent # => ... >
++ #
++ # For an element that has no parent, method +parent+ returns +nil+.
++ #
++ # === Children
++ #
++ # An element has zero or more children.
++ # The children are an ordered collection
++ # of all objects whose parent is the element itself.
++ #
++ # The children may include any combination of elements, text, comments,
++ # processing instructions, and CDATA.
++ # (This example keeps things clean by controlling whitespace
++ # via a +context+ setting.)
++ #
++ # xml_string = <<-EOT
++ #
++ #
++ # text 0
++ #
++ #
++ #
++ #
++ # text 1
++ #
++ #
++ #
++ #
++ # EOT
++ # context = {ignore_whitespace_nodes: :all, compress_whitespace: :all}
++ # d = REXML::Document.new(xml_string, context)
++ # root = d.root
++ # root.children.size # => 10
++ # root.each {|child| p "#{child.class}: #{child}" }
++ #
++ # Output:
++ #
++ # "REXML::Element: "
++ # "REXML::Text: \n text 0\n "
++ # "REXML::Comment: comment 0"
++ # "REXML::Instruction: "
++ # "REXML::CData: cdata 0"
++ # "REXML::Element: "
++ # "REXML::Text: \n text 1\n "
++ # "REXML::Comment: comment 1"
++ # "REXML::Instruction: "
++ # "REXML::CData: cdata 1"
++ #
++ # A child may be added using inherited methods
++ # Parent#insert_before or Parent#insert_after:
++ #
++ # xml_string = ''
++ # d = REXML::Document.new(xml_string)
++ # root = d.root
++ # c = d.root[1] # =>
++ # root.insert_before(c, REXML::Element.new('b'))
++ # root.to_a # => [, , , ]
++ #
++ # A child may be replaced using Parent#replace_child:
++ #
++ # root.replace_child(c, REXML::Element.new('x'))
++ # root.to_a # => [, , , ]
++ #
++ # A child may be removed using Parent#delete:
++ #
++ # x = root[2] # =>
++ # root.delete(x)
++ # root.to_a # => [, , ]
++ #
++ # === Siblings
++ #
++ # An element has zero or more siblings,
++ # which are the other children of the element's parent.
++ #
++ # In the example above, element +ele_1+ is between a CDATA sibling
++ # and a text sibling:
++ #
++ # ele_1 = root[5] # =>
++ # ele_1.previous_sibling # => "cdata 0"
++ # ele_1.next_sibling # => "\n text 1\n "
++ #
++ # === \Attributes
++ #
++ # An element has zero or more named attributes.
++ #
++ # A new element has no attributes:
++ #
++ # e = REXML::Element.new('foo')
++ # e.attributes # => {}
++ #
++ # Attributes may be added:
++ #
++ # e.add_attribute('bar', 'baz')
++ # e.add_attribute('bat', 'bam')
++ # e.attributes.size # => 2
++ # e['bar'] # => "baz"
++ # e['bat'] # => "bam"
++ #
++ # An existing attribute may be modified:
++ #
++ # e.add_attribute('bar', 'bad')
++ # e.attributes.size # => 2
++ # e['bar'] # => "bad"
++ #
++ # An existing attribute may be deleted:
++ #
++ # e.delete_attribute('bar')
++ # e.attributes.size # => 1
++ # e['bar'] # => nil
++ #
++ # == What's Here
++ #
++ # To begin with, what's elsewhere?
++ #
++ # \Class \REXML::Element inherits from its ancestor classes:
++ #
++ # - REXML::Child
++ # - REXML::Parent
++ #
++ # \REXML::Element itself and its ancestors also include modules:
++ #
++ # - {Enumerable}[https://docs.ruby-lang.org/en/master/Enumerable.html]
++ # - REXML::Namespace
++ # - REXML::Node
++ # - REXML::XMLTokens
++ #
++ # === Methods for Creating an \Element
++ #
++ # ::new:: Returns a new empty element.
++ # #clone:: Returns a clone of another element.
++ #
++ # === Methods for Attributes
++ #
++ # {[attribute_name]}[#method-i-5B-5D]:: Returns an attribute value.
++ # #add_attribute:: Adds a new attribute.
++ # #add_attributes:: Adds multiple new attributes.
++ # #attribute:: Returns the attribute value for a given name and optional namespace.
++ # #delete_attribute:: Removes an attribute.
++ #
++ # === Methods for Children
++ #
++ # {[index]}[#method-i-5B-5D]:: Returns the child at the given offset.
++ # #add_element:: Adds an element as the last child.
++ # #delete_element:: Deletes a child element.
++ # #each_element:: Calls the given block with each child element.
++ # #each_element_with_attribute:: Calls the given block with each child element
++ # that meets given criteria,
++ # which can include the attribute name.
++ # #each_element_with_text:: Calls the given block with each child element
++ # that meets given criteria,
++ # which can include text.
++ # #get_elements:: Returns an array of element children that match a given xpath.
++ #
++ # === Methods for \Text Children
++ #
++ # #add_text:: Adds a text node to the element.
++ # #get_text:: Returns a text node that meets specified criteria.
++ # #text:: Returns the text string from the first node that meets specified criteria.
++ # #texts:: Returns an array of the text children of the element.
++ # #text=:: Adds, removes, or replaces the first text child of the element
++ #
++ # === Methods for Other Children
++ #
++ # #cdatas:: Returns an array of the cdata children of the element.
++ # #comments:: Returns an array of the comment children of the element.
++ # #instructions:: Returns an array of the instruction children of the element.
++ #
++ # === Methods for Namespaces
++ #
++ # #add_namespace:: Adds a namespace to the element.
++ # #delete_namespace:: Removes a namespace from the element.
++ # #namespace:: Returns the string namespace URI for the element.
++ # #namespaces:: Returns a hash of all defined namespaces in the element.
++ # #prefixes:: Returns an array of the string prefixes (names)
++ # of all defined namespaces in the element
++ #
++ # === Methods for Querying
++ #
++ # #document:: Returns the document, if any, that the element belongs to.
++ # #root:: Returns the most distant element (not document) ancestor of the element.
++ # #root_node:: Returns the most distant ancestor of the element.
++ # #xpath:: Returns the string xpath to the element
++ # relative to the most distant parent
++ # #has_attributes?:: Returns whether the element has attributes.
++ # #has_elements?:: Returns whether the element has elements.
++ # #has_text?:: Returns whether the element has text.
++ # #next_element:: Returns the next sibling that is an element.
++ # #previous_element:: Returns the previous sibling that is an element.
++ # #raw:: Returns whether raw mode is set for the element.
++ # #whitespace:: Returns whether whitespace is respected for the element.
++ # #ignore_whitespace_nodes:: Returns whether whitespace nodes
++ # are to be ignored for the element.
++ # #node_type:: Returns symbol :element.
++ #
++ # === One More Method
++ #
++ # #inspect:: Returns a string representation of the element.
++ #
++ # === Accessors
++ #
++ # #elements:: Returns the REXML::Elements object for the element.
++ # #attributes:: Returns the REXML::Attributes object for the element.
++ # #context:: Returns or sets the context hash for the element.
++ #
+ class Element < Parent
+ include Namespace
+
+@@ -30,32 +280,42 @@ module REXML
+ # whitespace handling.
+ attr_accessor :context
+
+- # Constructor
+- # arg::
+- # if not supplied, will be set to the default value.
+- # If a String, the name of this object will be set to the argument.
+- # If an Element, the object will be shallowly cloned; name,
+- # attributes, and namespaces will be copied. Children will +not+ be
+- # copied.
+- # parent::
+- # if supplied, must be a Parent, and will be used as
+- # the parent of this object.
+- # context::
+- # If supplied, must be a hash containing context items. Context items
+- # include:
+- # * :respect_whitespace the value of this is :+all+ or an array of
+- # strings being the names of the elements to respect
+- # whitespace for. Defaults to :+all+.
+- # * :compress_whitespace the value can be :+all+ or an array of
+- # strings being the names of the elements to ignore whitespace on.
+- # Overrides :+respect_whitespace+.
+- # * :ignore_whitespace_nodes the value can be :+all+ or an array
+- # of strings being the names of the elements in which to ignore
+- # whitespace-only nodes. If this is set, Text nodes which contain only
+- # whitespace will not be added to the document tree.
+- # * :raw can be :+all+, or an array of strings being the names of
+- # the elements to process in raw mode. In raw mode, special
+- # characters in text is not converted to or from entities.
++ # :call-seq:
++ # Element.new(name = 'UNDEFINED', parent = nil, context = nil) -> new_element
++ # Element.new(element, parent = nil, context = nil) -> new_element
++ #
++ # Returns a new \REXML::Element object.
++ #
++ # When no arguments are given,
++ # returns an element with name 'UNDEFINED':
++ #
++ # e = REXML::Element.new # =>
++ # e.class # => REXML::Element
++ # e.name # => "UNDEFINED"
++ #
++ # When only argument +name+ is given,
++ # returns an element of the given name:
++ #
++ # REXML::Element.new('foo') # =>
++ #
++ # When only argument +element+ is given, it must be an \REXML::Element object;
++ # returns a shallow copy of the given element:
++ #
++ # e0 = REXML::Element.new('foo')
++ # e1 = REXML::Element.new(e0) # =>
++ #
++ # When argument +parent+ is also given, it must be an REXML::Parent object:
++ #
++ # e = REXML::Element.new('foo', REXML::Parent.new)
++ # e.parent # => #]>
++ #
++ # When argument +context+ is also given, it must be a hash
++ # representing the context for the element;
++ # see {Element Context}[../doc/rexml/context_rdoc.html]:
++ #
++ # e = REXML::Element.new('foo', nil, {raw: :all})
++ # e.context # => {:raw=>:all}
++ #
+ def initialize( arg = UNDEFINED, parent=nil, context=nil )
+ super(parent)
+
+@@ -74,6 +334,27 @@ module REXML
+ end
+ end
+
++ # :call-seq:
++ # inspect -> string
++ #
++ # Returns a string representation of the element.
++ #
++ # For an element with no attributes and no children, shows the element name:
++ #
++ # REXML::Element.new.inspect # => ""
++ #
++ # Shows attributes, if any:
++ #
++ # e = REXML::Element.new('foo')
++ # e.add_attributes({'bar' => 0, 'baz' => 1})
++ # e.inspect # => ""
++ #
++ # Shows an ellipsis (...), if there are child elements:
++ #
++ # e.add_element(REXML::Element.new('bar'))
++ # e.add_element(REXML::Element.new('baz'))
++ # e.inspect # => " ... >"
++ #
+ def inspect
+ rv = "<#@expanded_name"
+
+@@ -89,60 +370,118 @@ module REXML
+ end
+ end
+
+-
+- # Creates a shallow copy of self.
+- # d = Document.new ""
+- # new_a = d.root.clone
+- # puts new_a # => ""
++ # :call-seq:
++ # clone -> new_element
++ #
++ # Returns a shallow copy of the element, containing the name and attributes,
++ # but not the parent or children:
++ #
++ # e = REXML::Element.new('foo')
++ # e.add_attributes({'bar' => 0, 'baz' => 1})
++ # e.clone # =>
++ #
+ def clone
+ self.class.new self
+ end
+
+- # Evaluates to the root node of the document that this element
+- # belongs to. If this element doesn't belong to a document, but does
+- # belong to another Element, the parent's root will be returned, until the
+- # earliest ancestor is found.
+- #
+- # Note that this is not the same as the document element.
+- # In the following example, is the document element, and the root
+- # node is the parent node of the document element. You may ask yourself
+- # why the root node is useful: consider the doctype and XML declaration,
+- # and any processing instructions before the document element... they
+- # are children of the root node, or siblings of the document element.
+- # The only time this isn't true is when an Element is created that is
+- # not part of any Document. In this case, the ancestor that has no
+- # parent acts as the root node.
+- # d = Document.new ''
+- # a = d[1] ; c = a[1][1]
+- # d.root_node == d # TRUE
+- # a.root_node # namely, d
+- # c.root_node # again, d
++ # :call-seq:
++ # root_node -> document or element
++ #
++ # Returns the most distant ancestor of +self+.
++ #
++ # When the element is part of a document,
++ # returns the root node of the document.
++ # Note that the root node is different from the document element;
++ # in this example +a+ is document element and the root node is its parent:
++ #
++ # d = REXML::Document.new('')
++ # top_element = d.first # => ... >
++ # child = top_element.first # => ... >
++ # d.root_node == d # => true
++ # top_element.root_node == d # => true
++ # child.root_node == d # => true
++ #
++ # When the element is not part of a document, but does have ancestor elements,
++ # returns the most distant ancestor element:
++ #
++ # e0 = REXML::Element.new('foo')
++ # e1 = REXML::Element.new('bar')
++ # e1.parent = e0
++ # e2 = REXML::Element.new('baz')
++ # e2.parent = e1
++ # e2.root_node == e0 # => true
++ #
++ # When the element has no ancestor elements,
++ # returns +self+:
++ #
++ # e = REXML::Element.new('foo')
++ # e.root_node == e # => true
++ #
++ # Related: #root, #document.
++ #
+ def root_node
+ parent.nil? ? self : parent.root_node
+ end
+
++ # :call-seq:
++ # root -> element
++ #
++ # Returns the most distant _element_ (not document) ancestor of the element:
++ #
++ # d = REXML::Document.new('')
++ # top_element = d.first
++ # child = top_element.first
++ # top_element.root == top_element # => true
++ # child.root == top_element # => true
++ #
++ # For a document, returns the topmost element:
++ #
++ # d.root == top_element # => true
++ #
++ # Related: #root_node, #document.
++ #
+ def root
+ return elements[1] if self.kind_of? Document
+ return self if parent.kind_of? Document or parent.nil?
+ return parent.root
+ end
+
+- # Evaluates to the document to which this element belongs, or nil if this
+- # element doesn't belong to a document.
++ # :call-seq:
++ # document -> document or nil
++ #
++ # If the element is part of a document, returns that document:
++ #
++ # d = REXML::Document.new('')
++ # top_element = d.first
++ # child = top_element.first
++ # top_element.document == d # => true
++ # child.document == d # => true
++ #
++ # If the element is not part of a document, returns +nil+:
++ #
++ # REXML::Element.new.document # => nil
++ #
++ # For a document, returns +self+:
++ #
++ # d.document == d # => true
++ #
++ # Related: #root, #root_node.
++ #
+ def document
+ rt = root
+ rt.parent if rt
+ end
+
+- # Evaluates to +true+ if whitespace is respected for this element. This
+- # is the case if:
+- # 1. Neither :+respect_whitespace+ nor :+compress_whitespace+ has any value
+- # 2. The context has :+respect_whitespace+ set to :+all+ or
+- # an array containing the name of this element, and
+- # :+compress_whitespace+ isn't set to :+all+ or an array containing the
+- # name of this element.
+- # The evaluation is tested against +expanded_name+, and so is namespace
+- # sensitive.
++ # :call-seq:
++ # whitespace
++ #
++ # Returns +true+ if whitespace is respected for this element,
++ # +false+ otherwise.
++ #
++ # See {Element Context}[../doc/rexml/context_rdoc.html].
++ #
++ # The evaluation is tested against the element's +expanded_name+,
++ # and so is namespace-sensitive.
+ def whitespace
+ @whitespace = nil
+ if @context
+@@ -159,6 +498,13 @@ module REXML
+ @whitespace
+ end
+
++ # :call-seq:
++ # ignore_whitespace_nodes
++ #
++ # Returns +true+ if whitespace nodes are ignored for the element.
++ #
++ # See {Element Context}[../doc/rexml/context_rdoc.html].
++ #
+ def ignore_whitespace_nodes
+ @ignore_whitespace_nodes = false
+ if @context
+@@ -170,9 +516,12 @@ module REXML
+ end
+ end
+
+- # Evaluates to +true+ if raw mode is set for this element. This
+- # is the case if the context has :+raw+ set to :+all+ or
+- # an array containing the name of this element.
++ # :call-seq:
++ # raw
++ #
++ # Returns +true+ if raw mode is set for the element.
++ #
++ # See {Element Context}[../doc/rexml/context_rdoc.html].
+ #
+ # The evaluation is tested against +expanded_name+, and so is namespace
+ # sensitive.
+@@ -180,7 +529,7 @@ module REXML
+ @raw = (@context and @context[:raw] and
+ (@context[:raw] == :all or
+ @context[:raw].include? expanded_name))
+- @raw
++ @raw
+ end
+
+ #once :whitespace, :raw, :ignore_whitespace_nodes
+@@ -189,10 +538,25 @@ module REXML
+ # Namespaces #
+ #################################################
+
+- # Evaluates to an +Array+ containing the prefixes (names) of all defined
+- # namespaces at this context node.
+- # doc = Document.new("")
+- # doc.elements['//b'].prefixes # -> ['x', 'y']
++ # :call-seq:
++ # prefixes -> array_of_namespace_prefixes
++ #
++ # Returns an array of the string prefixes (names) of all defined namespaces
++ # in the element and its ancestors:
++ #
++ # xml_string = <<-EOT
++ #
++ #
++ #
++ #
++ #
++ #
++ # EOT
++ # d = REXML::Document.new(xml_string, {compress_whitespace: :all})
++ # d.elements['//a'].prefixes # => ["x", "y"]
++ # d.elements['//b'].prefixes # => ["x", "y"]
++ # d.elements['//c'].prefixes # => ["x", "y", "z"]
++ #
+ def prefixes
+ prefixes = []
+ prefixes = parent.prefixes if parent
+@@ -200,6 +564,25 @@ module REXML
+ return prefixes
+ end
+
++ # :call-seq:
++ # namespaces -> array_of_namespace_names
++ #
++ # Returns a hash of all defined namespaces
++ # in the element and its ancestors:
++ #
++ # xml_string = <<-EOT
++ #
++ #
++ #
++ #
++ #
++ #
++ # EOT
++ # d = REXML::Document.new(xml_string)
++ # d.elements['//a'].namespaces # => {"x"=>"1", "y"=>"2"}
++ # d.elements['//b'].namespaces # => {"x"=>"1", "y"=>"2"}
++ # d.elements['//c'].namespaces # => {"x"=>"1", "y"=>"2", "z"=>"3"}
++ #
+ def namespaces
+ namespaces = {}
+ namespaces = parent.namespaces if parent
+@@ -207,19 +590,26 @@ module REXML
+ return namespaces
+ end
+
+- # Evaluates to the URI for a prefix, or the empty string if no such
+- # namespace is declared for this element. Evaluates recursively for
+- # ancestors. Returns the default namespace, if there is one.
+- # prefix::
+- # the prefix to search for. If not supplied, returns the default
+- # namespace if one exists
+- # Returns::
+- # the namespace URI as a String, or nil if no such namespace
+- # exists. If the namespace is undefined, returns an empty string
+- # doc = Document.new("")
+- # b = doc.elements['//b']
+- # b.namespace # -> '1'
+- # b.namespace("y") # -> '2'
++ # :call-seq:
++ # namespace(prefix = nil) -> string_uri or nil
++ #
++ # Returns the string namespace URI for the element,
++ # possibly deriving from one of its ancestors.
++ #
++ # xml_string = <<-EOT
++ #
++ #
++ #
++ #
++ #
++ #
++ # EOT
++ # d = REXML::Document.new(xml_string)
++ # b = d.elements['//b']
++ # b.namespace # => "1"
++ # b.namespace('y') # => "2"
++ # b.namespace('nosuch') # => nil
++ #
+ def namespace(prefix=nil)
+ if prefix.nil?
+ prefix = prefix()
+@@ -235,19 +625,24 @@ module REXML
+ return ns
+ end
+
+- # Adds a namespace to this element.
+- # prefix::
+- # the prefix string, or the namespace URI if +uri+ is not
+- # supplied
+- # uri::
+- # the namespace URI. May be nil, in which +prefix+ is used as
+- # the URI
+- # Evaluates to: this Element
+- # a = Element.new("a")
+- # a.add_namespace("xmlns:foo", "bar" )
+- # a.add_namespace("foo", "bar") # shorthand for previous line
+- # a.add_namespace("twiddle")
+- # puts a #->
++ # :call-seq:
++ # add_namespace(prefix, uri = nil) -> self
++ #
++ # Adds a namespace to the element; returns +self+.
++ #
++ # With the single argument +prefix+,
++ # adds a namespace using the given +prefix+ and the namespace URI:
++ #
++ # e = REXML::Element.new('foo')
++ # e.add_namespace('bar')
++ # e.namespaces # => {"xmlns"=>"bar"}
++ #
++ # With both arguments +prefix+ and +uri+ given,
++ # adds a namespace using both arguments:
++ #
++ # e.add_namespace('baz', 'bat')
++ # e.namespaces # => {"xmlns"=>"bar", "baz"=>"bat"}
++ #
+ def add_namespace( prefix, uri=nil )
+ unless uri
+ @attributes["xmlns"] = prefix
+@@ -258,16 +653,28 @@ module REXML
+ self
+ end
+
+- # Removes a namespace from this node. This only works if the namespace is
+- # actually declared in this node. If no argument is passed, deletes the
+- # default namespace.
++ # :call-seq:
++ # delete_namespace(namespace = 'xmlns') -> self
++ #
++ # Removes a namespace from the element.
++ #
++ # With no argument, removes the default namespace:
++ #
++ # d = REXML::Document.new ""
++ # d.to_s # => ""
++ # d.root.delete_namespace # =>
++ # d.to_s # => ""
++ #
++ # With argument +namespace+, removes the specified namespace:
++ #
++ # d.root.delete_namespace('foo')
++ # d.to_s # => ""
++ #
++ # Does nothing if no such namespace is found:
++ #
++ # d.root.delete_namespace('nosuch')
++ # d.to_s # => ""
+ #
+- # Evaluates to: this element
+- # doc = Document.new ""
+- # doc.root.delete_namespace
+- # puts doc # ->
+- # doc.root.delete_namespace 'foo'
+- # puts doc # ->
+ def delete_namespace namespace="xmlns"
+ namespace = "xmlns:#{namespace}" unless namespace == 'xmlns'
+ attribute = attributes.get_attribute(namespace)
+@@ -279,20 +686,40 @@ module REXML
+ # Elements #
+ #################################################
+
+- # Adds a child to this element, optionally setting attributes in
+- # the element.
+- # element::
+- # optional. If Element, the element is added.
+- # Otherwise, a new Element is constructed with the argument (see
+- # Element.initialize).
+- # attrs::
+- # If supplied, must be a Hash containing String name,value
+- # pairs, which will be used to set the attributes of the new Element.
+- # Returns:: the Element that was added
+- # el = doc.add_element 'my-tag'
+- # el = doc.add_element 'my-tag', {'attr1'=>'val1', 'attr2'=>'val2'}
+- # el = Element.new 'my-tag'
+- # doc.add_element el
++ # :call-seq:
++ # add_element(name, attributes = nil) -> new_element
++ # add_element(element, attributes = nil) -> element
++ #
++ # Adds a child element, optionally setting attributes
++ # on the added element; returns the added element.
++ #
++ # With string argument +name+, creates a new element with that name
++ # and adds the new element as a child:
++ #
++ # e0 = REXML::Element.new('foo')
++ # e0.add_element('bar')
++ # e0[0] # =>
++ #
++ #
++ # With argument +name+ and hash argument +attributes+,
++ # sets attributes on the new element:
++ #
++ # e0.add_element('baz', {'bat' => '0', 'bam' => '1'})
++ # e0[1] # =>
++ #
++ # With element argument +element+, adds that element as a child:
++ #
++ # e0 = REXML::Element.new('foo')
++ # e1 = REXML::Element.new('bar')
++ # e0.add_element(e1)
++ # e0[0] # =>
++ #
++ # With argument +element+ and hash argument +attributes+,
++ # sets attributes on the added element:
++ #
++ # e0.add_element(e1, {'bat' => '0', 'bam' => '1'})
++ # e0[1] # =>
++ #
+ def add_element element, attrs=nil
+ raise "First argument must be either an element name, or an Element object" if element.nil?
+ el = @elements.add(element)
+@@ -302,52 +729,112 @@ module REXML
+ el
+ end
+
++ # :call-seq:
++ # delete_element(index) -> removed_element or nil
++ # delete_element(element) -> removed_element or nil
++ # delete_element(xpath) -> removed_element or nil
++ #
+ # Deletes a child element.
+- # element::
+- # Must be an +Element+, +String+, or +Integer+. If Element,
+- # the element is removed. If String, the element is found (via XPath)
+- # and removed. This means that any parent can remove any
+- # descendant. If Integer, the Element indexed by that number will be
+- # removed.
+- # Returns:: the element that was removed.
+- # doc.delete_element "/a/b/c[@id='4']"
+- # doc.delete_element doc.elements["//k"]
+- # doc.delete_element 1
++ #
++ # When 1-based integer argument +index+ is given,
++ # removes and returns the child element at that offset if it exists;
++ # indexing does not include text nodes;
++ # returns +nil+ if the element does not exist:
++ #
++ # d = REXML::Document.new 'text'
++ # a = d.root # => ... >
++ # a.delete_element(1) # =>
++ # a.delete_element(1) # =>
++ # a.delete_element(1) # => nil
++ #
++ # When element argument +element+ is given,
++ # removes and returns that child element if it exists,
++ # otherwise returns +nil+:
++ #
++ # d = REXML::Document.new 'text'
++ # a = d.root # => ... >
++ # c = a[2] # =>
++ # a.delete_element(c) # =>
++ # a.delete_element(c) # => nil
++ #
++ # When xpath argument +xpath+ is given,
++ # removes and returns the element at xpath if it exists,
++ # otherwise returns +nil+:
++ #
++ # d = REXML::Document.new 'text'
++ # a = d.root # => ... >
++ # a.delete_element('//c') # =>
++ # a.delete_element('//c') # => nil
++ #
+ def delete_element element
+ @elements.delete element
+ end
+
+- # Evaluates to +true+ if this element has at least one child Element
+- # doc = Document.new "Text"
+- # doc.root.has_elements # -> true
+- # doc.elements["/a/b"].has_elements # -> false
+- # doc.elements["/a/c"].has_elements # -> false
++ # :call-seq:
++ # has_elements?
++ #
++ # Returns +true+ if the element has one or more element children,
++ # +false+ otherwise:
++ #
++ # d = REXML::Document.new 'text'
++ # a = d.root # => ... >
++ # a.has_elements? # => true
++ # b = a[0] # =>
++ # b.has_elements? # => false
++ #
+ def has_elements?
+ !@elements.empty?
+ end
+
+- # Iterates through the child elements, yielding for each Element that
+- # has a particular attribute set.
+- # key::
+- # the name of the attribute to search for
+- # value::
+- # the value of the attribute
+- # max::
+- # (optional) causes this method to return after yielding
+- # for this number of matching children
+- # name::
+- # (optional) if supplied, this is an XPath that filters
+- # the children to check.
+- #
+- # doc = Document.new ""
+- # # Yields b, c, d
+- # doc.root.each_element_with_attribute( 'id' ) {|e| p e}
+- # # Yields b, d
+- # doc.root.each_element_with_attribute( 'id', '1' ) {|e| p e}
+- # # Yields b
+- # doc.root.each_element_with_attribute( 'id', '1', 1 ) {|e| p e}
+- # # Yields d
+- # doc.root.each_element_with_attribute( 'id', '1', 0, 'd' ) {|e| p e}
++ # :call-seq:
++ # each_element_with_attribute(attr_name, value = nil, max = 0, xpath = nil) {|e| ... }
++ #
++ # Calls the given block with each child element that meets given criteria.
++ #
++ # When only string argument +attr_name+ is given,
++ # calls the block with each child element that has that attribute:
++ #
++ # d = REXML::Document.new ''
++ # a = d.root
++ # a.each_element_with_attribute('id') {|e| p e }
++ #
++ # Output:
++ #
++ #
++ #
++ #
++ #
++ # With argument +attr_name+ and string argument +value+ given,
++ # calls the block with each child element that has that attribute
++ # with that value:
++ #
++ # a.each_element_with_attribute('id', '1') {|e| p e }
++ #
++ # Output:
++ #
++ #
++ #
++ #
++ # With arguments +attr_name+, +value+, and integer argument +max+ given,
++ # calls the block with at most +max+ child elements:
++ #
++ # a.each_element_with_attribute('id', '1', 1) {|e| p e }
++ #
++ # Output:
++ #
++ #
++ #
++ # With all arguments given, including +xpath+,
++ # calls the block with only those child elements
++ # that meet the first three criteria,
++ # and also match the given +xpath+:
++ #
++ # a.each_element_with_attribute('id', '1', 2, '//d') {|e| p e }
++ #
++ # Output:
++ #
++ #
++ #
+ def each_element_with_attribute( key, value=nil, max=0, name=nil, &block ) # :yields: Element
+ each_with_something( proc {|child|
+ if value.nil?
+@@ -358,27 +845,53 @@ module REXML
+ }, max, name, &block )
+ end
+
+- # Iterates through the children, yielding for each Element that
+- # has a particular text set.
+- # text::
+- # the text to search for. If nil, or not supplied, will iterate
+- # over all +Element+ children that contain at least one +Text+ node.
+- # max::
+- # (optional) causes this method to return after yielding
+- # for this number of matching children
+- # name::
+- # (optional) if supplied, this is an XPath that filters
+- # the children to check.
+- #
+- # doc = Document.new 'bbd'
+- # # Yields b, c, d
+- # doc.each_element_with_text {|e|p e}
+- # # Yields b, c
+- # doc.each_element_with_text('b'){|e|p e}
+- # # Yields b
+- # doc.each_element_with_text('b', 1){|e|p e}
+- # # Yields d
+- # doc.each_element_with_text(nil, 0, 'd'){|e|p e}
++ # :call-seq:
++ # each_element_with_text(text = nil, max = 0, xpath = nil) {|e| ... }
++ #
++ # Calls the given block with each child element that meets given criteria.
++ #
++ # With no arguments, calls the block with each child element that has text:
++ #
++ # d = REXML::Document.new 'bbd'
++ # a = d.root
++ # a.each_element_with_text {|e| p e }
++ #
++ # Output:
++ #
++ # ... >
++ # ... >
++ # ... >
++ #
++ # With the single string argument +text+,
++ # calls the block with each element that has exactly that text:
++ #
++ # a.each_element_with_text('b') {|e| p e }
++ #
++ # Output:
++ #
++ # ... >
++ # ... >
++ #
++ # With argument +text+ and integer argument +max+,
++ # calls the block with at most +max+ elements:
++ #
++ # a.each_element_with_text('b', 1) {|e| p e }
++ #
++ # Output:
++ #
++ # ... >
++ #
++ # With all arguments given, including +xpath+,
++ # calls the block with only those child elements
++ # that meet the first two criteria,
++ # and also match the given +xpath+:
++ #
++ # a.each_element_with_text('b', 2, '//c') {|e| p e }
++ #
++ # Output:
++ #
++ # ... >
++ #
+ def each_element_with_text( text=nil, max=0, name=nil, &block ) # :yields: Element
+ each_with_something( proc {|child|
+ if text.nil?
+@@ -389,35 +902,71 @@ module REXML
+ }, max, name, &block )
+ end
+
+- # Synonym for Element.elements.each
++ # :call-seq:
++ # each_element {|e| ... }
++ #
++ # Calls the given block with each child element:
++ #
++ # d = REXML::Document.new 'bbd'
++ # a = d.root
++ # a.each_element {|e| p e }
++ #
++ # Output:
++ #
++ # ... >
++ # ... >
++ # ... >
++ #
++ #
+ def each_element( xpath=nil, &block ) # :yields: Element
+ @elements.each( xpath, &block )
+ end
+
+- # Synonym for Element.to_a
+- # This is a little slower than calling elements.each directly.
+- # xpath:: any XPath by which to search for elements in the tree
+- # Returns:: an array of Elements that match the supplied path
++ # :call-seq:
++ # get_elements(xpath)
++ #
++ # Returns an array of the elements that match the given +xpath+:
++ #
++ # xml_string = <<-EOT
++ #
++ #
++ #
++ #
++ #
++ # EOT
++ # d = REXML::Document.new(xml_string)
++ # d.root.get_elements('//a') # => [ ... >, ]
++ #
+ def get_elements( xpath )
+ @elements.to_a( xpath )
+ end
+
+- # Returns the next sibling that is an element, or nil if there is
+- # no Element sibling after this one
+- # doc = Document.new 'text'
+- # doc.root.elements['b'].next_element #->
+- # doc.root.elements['c'].next_element #-> nil
++ # :call-seq:
++ # next_element
++ #
++ # Returns the next sibling that is an element if it exists,
++ # +niL+ otherwise:
++ #
++ # d = REXML::Document.new 'text'
++ # d.root.elements['b'].next_element #->
++ # d.root.elements['c'].next_element #-> nil
++ #
+ def next_element
+ element = next_sibling
+ element = element.next_sibling until element.nil? or element.kind_of? Element
+ return element
+ end
+
+- # Returns the previous sibling that is an element, or nil if there is
+- # no Element sibling prior to this one
+- # doc = Document.new 'text'
+- # doc.root.elements['c'].previous_element #->
+- # doc.root.elements['b'].previous_element #-> nil
++ # :call-seq:
++ # previous_element
++ #
++ # Returns the previous sibling that is an element if it exists,
++ # +niL+ otherwise:
++ #
++ # d = REXML::Document.new 'text'
++ # d.root.elements['c'].previous_element #->
++ # d.root.elements['b'].previous_element #-> nil
++ #
+ def previous_element
+ element = previous_sibling
+ element = element.previous_sibling until element.nil? or element.kind_of? Element
+@@ -429,36 +978,69 @@ module REXML
+ # Text #
+ #################################################
+
+- # Evaluates to +true+ if this element has at least one Text child
++ # :call-seq:
++ # has_text? -> true or false
++ #
++ # Returns +true+ if the element has one or more text noded,
++ # +false+ otherwise:
++ #
++ # d = REXML::Document.new 'text'
++ # a = d.root
++ # a.has_text? # => true
++ # b = a[0]
++ # b.has_text? # => false
++ #
+ def has_text?
+ not text().nil?
+ end
+
+- # A convenience method which returns the String value of the _first_
+- # child text element, if one exists, and +nil+ otherwise.
++ # :call-seq:
++ # text(xpath = nil) -> text_string or nil
++ #
++ # Returns the text string from the first text node child
++ # in a specified element, if it exists, +nil+ otherwise.
+ #
+- # Note that an element may have multiple Text elements, perhaps
+- # separated by other children. Be aware that this method only returns
+- # the first Text node.
++ # With no argument, returns the text from the first text node in +self+:
+ #
+- # This method returns the +value+ of the first text child node, which
+- # ignores the +raw+ setting, so always returns normalized text. See
+- # the Text::value documentation.
++ # d = REXML::Document.new "
some text this is bold! more text
"
++ # d.root.text.class # => String
++ # d.root.text # => "some text "
++ #
++ # With argument +xpath+, returns text from the first text node
++ # in the element that matches +xpath+:
++ #
++ # d.root.text(1) # => "this is bold!"
++ #
++ # Note that an element may have multiple text nodes,
++ # possibly separated by other non-text children, as above.
++ # Even so, the returned value is the string text from the first such node.
++ #
++ # Note also that the text note is retrieved by method get_text,
++ # and so is always normalized text.
+ #
+- # doc = Document.new "
some text this is bold! more text
"
+- # # The element 'p' has two text elements, "some text " and " more text".
+- # doc.root.text #-> "some text "
+ def text( path = nil )
+ rv = get_text(path)
+ return rv.value unless rv.nil?
+ nil
+ end
+
+- # Returns the first child Text node, if any, or +nil+ otherwise.
+- # This method returns the actual +Text+ node, rather than the String content.
+- # doc = Document.new "
some text this is bold! more text
"
+- # # The element 'p' has two text elements, "some text " and " more text".
+- # doc.root.get_text.value #-> "some text "
++ # :call-seq:
++ # get_text(xpath = nil) -> text_node or nil
++ #
++ # Returns the first text node child in a specified element, if it exists,
++ # +nil+ otherwise.
++ #
++ # With no argument, returns the first text node from +self+:
++ #
++ # d = REXML::Document.new "