From b9dfc77ee846ea2cee5637654bb733573f1bba14 Mon Sep 17 00:00:00 2001 From: youdie006 Date: Wed, 9 Sep 2026 09:51:24 +0900 Subject: [PATCH] Don't mutate the text node while pretty printing Pretty#write_text called gsub! and squeeze! on the string returned by Text#to_s, and Text#to_s returns the node's own string: @string when @raw, otherwise the memoized @normalized. So pretty printing rewrote the document it was printing. d = REXML::Document.new("hello world") d.write(out, 2) d.to_s # => "hello world" Use the non-destructive gsub and squeeze. The other four node.to_s call sites in the formatters only read the value. --- lib/rexml/formatters/pretty.rb | 5 ++--- test/formatter/test_pretty.rb | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 test/formatter/test_pretty.rb diff --git a/lib/rexml/formatters/pretty.rb b/lib/rexml/formatters/pretty.rb index a838d835..ff0e1022 100644 --- a/lib/rexml/formatters/pretty.rb +++ b/lib/rexml/formatters/pretty.rb @@ -86,9 +86,8 @@ def write_element(node, output) end def write_text( node, output ) - s = node.to_s() - s.gsub!(/\s/,' ') - s.squeeze!(" ") + # Not gsub!/squeeze!: Text#to_s returns the node's own string. + s = node.to_s().gsub(/\s/, ' ').squeeze(" ") s = wrap(s, @width - @level) s = indent_text(s, @level, " ", true) output << (' '*@level + s) diff --git a/test/formatter/test_pretty.rb b/test/formatter/test_pretty.rb new file mode 100644 index 00000000..a3e97d54 --- /dev/null +++ b/test/formatter/test_pretty.rb @@ -0,0 +1,34 @@ +module REXMLTests + class PrettyFormatterTest < Test::Unit::TestCase + def format(node, indentation=2) + formatter = REXML::Formatters::Pretty.new(indentation) + output = +"" + formatter.write(node, output) + output + end + + class TextTest < self + def test_source_document_is_not_modified + document = REXML::Document.new("hello world") + format(document) + assert_equal("hello world", document.to_s) + end + + def test_raw_text_value_is_not_modified + text = REXML::Text.new("hello world", true, nil, true) + format(text) + assert_equal("hello world", text.value) + end + + def test_whitespace_is_replaced_with_space + document = REXML::Document.new("x\ty") + assert_equal("\n \n x y\n \n", format(document)) + end + + def test_consecutive_spaces_are_squeezed + document = REXML::Document.new("x y") + assert_equal("\n \n x y\n \n", format(document)) + end + end + end +end