Ticket #87 (new enhancement)

Opened 2 years ago

Last modified 16 months ago

Store attributes as instances; defer to_s calls until serialization

Reported by: Phrogz Owned by: ser
Priority: normal Milestone: 3.2.0
Component: DOM Version: 3.1.5
Severity: minor Keywords: tostring delayed deferred delay
Cc: gavin.kistner@… Ruby version: 1.8.4
Operating system: All

Description

I want to be able to assign object instances as attribute values of an Element and have the to_s method of the instance called and used each time the Element is serialized, NOT when the value is assigned.

Theoretically, this would make document construction faster, but serialization a little slower. I *think* memory usage would go down. In any case, the benefits of this feature are not speed or memory changes, but rather the ability to use complex, mutable objects as attributes.

I would be fine if this was an option included at document creation, instead of the default behavior.

Following is a test case showcasing how I'd like it to behave (assuming default behavior instead of a per-document or per-element setting):

require 'test/unit' require 'rexml/document'

class Foo

attr_accessor :bar def to_s; bar.to_s; end

end

class MyTest? < Test::Unit::TestCase?

def test_deferred_tostring

d = REXML::Document.new d << REXML::Element.new( 'root' ) f = Foo.new f.bar = 'old' d.root.attributes[ 'foo' ] = f f.bar = 'new' output = "" d.write( output ) assert_equal( output, "<root foo='new'/>" )

end

end

Change History

Changed 2 years ago by Phrogz

Sorry, I keep forgetting the wiki formatting. Here's the test case again:

require 'test/unit'
require 'rexml/document'

class Foo
  attr_accessor :bar
  def to_s; bar.to_s; end
end

class MyTest < Test::Unit::TestCase
  def test_deferred_tostring
    d = REXML::Document.new
    d << REXML::Element.new( 'root' )
    f = Foo.new
    f.bar = 'old'
    d.root.attributes[ 'foo' ] = f
    f.bar = 'new'
    output = ""
    d.write( output )
    assert_equal( output, "<root foo='new'/>" )
  end
end

Changed 2 years ago by Phrogz

I'm on windows without diff available, but the patch seems to be as simple as removing the #to_s call on line 40 of attribute.rb:

  @value = second.to_s
  becomes
  @value = second

and adding it to line 105

  @normalized = Text::normalize( @value, doctype )
  becomes
  @normalized = Text::normalize( @value.to_s, doctype )

Changed 2 years ago by Phrogz

Ack, those should be lines 39 and 104 respectively (forgot that I had adding a debugging call in).

Changed 2 years ago by Phrogz

The rabbit hole goes deeper. My simple test didn't find another spot that would need to be patched: line 117 in attribute.rb:

@unnormalized = Text::unnormalize( @value, doctype )
becomes
@unnormalized = Text::unnormalize( @value.to_s, doctype )

I suppose I really should run all the rexml unit tests to find out if that's enough or not.

Changed 2 years ago by Phrogz

Hrm...I must be doing something wrong. When I leave the code in a state that I know is broken, there's no change to the unit tests. Either they don't cover attribute setting/getting, or the tests aren't being run against my changes.

I leave the full implementation and testing to you, then.

Changed 2 years ago by Phrogz

Finally, here's a slightly terser test case, with the proper order of arguments to assert_equal, and no need for an external class:

def test_deferred_tostring
  d = REXML::Document.new
  d << REXML::Element.new( 'root' )
  a = ['old']
  d.root.attributes[ 'foo' ] = a
  a[0] = 'new'
  d.write( output="" )
  assert_equal( "<root foo='new'/>", output )
end

Changed 16 months ago by ser

  • milestone set to 3.2.0
Note: See TracTickets for help on using tickets.