xmlss 0.4.1 → 1.0.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/Gemfile.lock +3 -3
  2. data/README.rdoc +4 -4
  3. data/bench/profiler_runner.rb +1 -1
  4. data/examples/example_workbook.rb +1 -1
  5. data/examples/simple.rb +18 -1
  6. data/examples/simple.xml +22 -0
  7. data/lib/xmlss/element/cell.rb +52 -1
  8. data/lib/xmlss/element/column.rb +2 -0
  9. data/lib/xmlss/element/row.rb +2 -0
  10. data/lib/xmlss/element/worksheet.rb +14 -6
  11. data/lib/xmlss/element_stack.rb +69 -0
  12. data/lib/xmlss/style/alignment.rb +2 -0
  13. data/lib/xmlss/style/base.rb +2 -0
  14. data/lib/xmlss/style/border.rb +10 -0
  15. data/lib/xmlss/style/font.rb +2 -0
  16. data/lib/xmlss/style/interior.rb +2 -0
  17. data/lib/xmlss/style/number_format.rb +2 -0
  18. data/lib/xmlss/style/protection.rb +2 -0
  19. data/lib/xmlss/version.rb +1 -1
  20. data/lib/xmlss/workbook.rb +75 -58
  21. data/lib/xmlss/writer.rb +244 -0
  22. data/test/element/cell_test.rb +60 -1
  23. data/test/element/column_test.rb +5 -0
  24. data/test/element/row_test.rb +5 -0
  25. data/test/element/worksheet_test.rb +9 -4
  26. data/test/element_stack_test.rb +117 -0
  27. data/test/style/alignment_test.rb +5 -0
  28. data/test/style/base_test.rb +5 -0
  29. data/test/style/border_test.rb +26 -2
  30. data/test/style/font_test.rb +5 -0
  31. data/test/style/interior_test.rb +5 -0
  32. data/test/style/number_format_test.rb +5 -0
  33. data/test/style/protection_test.rb +6 -1
  34. data/test/workbook_test.rb +29 -13
  35. data/test/writer_test.rb +413 -0
  36. data/xmlss.gemspec +1 -1
  37. metadata +30 -25
  38. data/lib/xmlss/element/data.rb +0 -58
  39. data/lib/xmlss/undies_writer.rb +0 -175
  40. data/test/element/data_test.rb +0 -67
  41. data/test/undies_writer_test.rb +0 -372
@@ -0,0 +1,244 @@
1
+ require 'undies'
2
+ require 'stringio'
3
+
4
+ module Xmlss
5
+ class Writer
6
+
7
+ class Markup; end
8
+
9
+ # Xmlss uses Undies to stream its xml markup
10
+ # The Undies writer is responsible for driving the Undies API to generate
11
+ # the xmlss xml markup for the workbook.
12
+ # Because order doesn't matter when defining style and worksheet elements,
13
+ # the writer has to buffer the style and worksheet markup separately,
14
+ # then put them together according to Xmlss spec to build the final
15
+ # workbook markup.
16
+
17
+ XML_NS = "xmlns"
18
+ SHEET_NS = "ss"
19
+ NS_URI = "urn:schemas-microsoft-com:office:spreadsheet"
20
+ LB = "
"
21
+
22
+ def self.attributes(thing, *attrs)
23
+ [*attrs].flatten.inject({}) do |xattrs, a|
24
+ xattrs.merge(if !(xv = self.coerce(thing.send(a))).nil?
25
+ {xmlss_attribute_name(a) => xv.to_s}
26
+ else
27
+ {}
28
+ end)
29
+ end
30
+ end
31
+
32
+ def self.xmlss_attribute_name(attr_name)
33
+ "#{SHEET_NS}:#{self.classify(attr_name)}"
34
+ end
35
+
36
+ def self.classify(underscored_string)
37
+ underscored_string.
38
+ to_s.downcase.
39
+ split("_").
40
+ collect{|part| part.capitalize}.
41
+ join('')
42
+ end
43
+
44
+ def self.coerce(value)
45
+ if value == true
46
+ 1
47
+ elsif ["",false].include?(value)
48
+ # don't include false or empty string values
49
+ nil
50
+ else
51
+ value
52
+ end
53
+ end
54
+
55
+ attr_reader :styles_markup
56
+ attr_reader :worksheets_markup
57
+
58
+ def initialize(output_opts={})
59
+ @opts = output_opts || {}
60
+
61
+ @styles_markup = Markup.new(@opts.merge(:pp_level => 2))
62
+ @worksheets_markup = Markup.new(@opts.merge(:pp_level => 1))
63
+ end
64
+
65
+ def write(element)
66
+ self.send(element.class.writer, element)
67
+ end
68
+
69
+ def push(template)
70
+ self.send("#{template}_markup").push
71
+ end
72
+
73
+ def pop(template)
74
+ self.send("#{template}_markup").pop
75
+ end
76
+
77
+ def flush
78
+ # flush the worksheets markup first b/c it may add style elements to
79
+ # the styles markup
80
+ worksheets_markup.flush
81
+ styles_markup.flush
82
+ self
83
+ end
84
+
85
+ # return the full workbook markup, combining the buffers to xmlss spec
86
+ def workbook
87
+ self.flush
88
+ "".tap do |markup|
89
+ Undies::Template.new(Undies::Source.new(Proc.new do
90
+ __ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
91
+ _Workbook(XML_NS => NS_URI, "#{XML_NS}:#{SHEET_NS}" => NS_URI) {
92
+ _Styles {
93
+ __partial styles
94
+ }
95
+ __partial worksheets
96
+ }
97
+ end), {
98
+ :styles => styles_markup.to_s,
99
+ :worksheets => worksheets_markup.to_s
100
+ }, Undies::Output.new(StringIO.new(markup), @opts))
101
+ end.strip
102
+ end
103
+
104
+ # workbook style markup directives
105
+
106
+ def alignment(alignment)
107
+ styles_markup.template._Alignment(self.class.attributes(alignment, [
108
+ :horizontal, :vertical, :wrap_text, :rotate
109
+ ]))
110
+ end
111
+
112
+ def border(border)
113
+ styles_markup.template._Border(self.class.attributes(border, [
114
+ :color, :position, :weight, :line_style
115
+ ]))
116
+ end
117
+
118
+ def borders(borders)
119
+ styles_markup.template._Borders
120
+ end
121
+
122
+ def font(font)
123
+ styles_markup.template._Font(self.class.attributes(font, [
124
+ :bold, :color, :italic, :size, :shadow, :font_name,
125
+ :strike_through, :underline, :vertical_align
126
+ ]))
127
+ end
128
+
129
+ def interior(interior)
130
+ styles_markup.template._Interior(self.class.attributes(interior, [
131
+ :color, :pattern, :pattern_color
132
+ ]))
133
+ end
134
+
135
+ def number_format(number_format)
136
+ styles_markup.template._NumberFormat(self.class.attributes(number_format, [
137
+ :format
138
+ ]))
139
+ end
140
+
141
+ def protection(protection)
142
+ styles_markup.template._Protection(self.class.attributes(protection, [
143
+ :protect
144
+ ]))
145
+ end
146
+
147
+ def style(style)
148
+ styles_markup.template._Style(self.class.attributes(style, [
149
+ :i_d
150
+ ]))
151
+ end
152
+
153
+ # workbook element markup directives
154
+
155
+ def cell(cell)
156
+ # write the cell markup and push
157
+ worksheets_markup.template._Cell(self.class.attributes(cell, [
158
+ [:index, :style_i_d, :formula, :h_ref, :merge_across, :merge_down]
159
+ ]))
160
+ push(:worksheets)
161
+
162
+ # write nested data markup and push
163
+ worksheets_markup.template._Data(self.class.attributes(cell, [
164
+ [:type]
165
+ ]))
166
+ push(:worksheets)
167
+
168
+ # write data value
169
+ worksheets_markup.template.__ Undies::Template.
170
+ escape_html(cell.data_xml_value).
171
+ gsub(/(\r|\n)+/, LB)
172
+
173
+ pop(:worksheets)
174
+ pop(:worksheets)
175
+ end
176
+
177
+ def row(row)
178
+ worksheets_markup.template._Row(self.class.attributes(row, [
179
+ [:style_i_d, :height, :auto_fit_height, :hidden]
180
+ ]))
181
+ end
182
+
183
+ def column(column)
184
+ worksheets_markup.template._Column(self.class.attributes(column, [
185
+ [:style_i_d, :width, :auto_fit_width, :hidden]
186
+ ]))
187
+ end
188
+
189
+ def worksheet(worksheet)
190
+ # flush any previous worksheet markup
191
+ worksheets_markup.flush
192
+
193
+ # write the worksheet markup and push
194
+ worksheets_markup.template._Worksheet(self.class.attributes(worksheet, [
195
+ [:name]
196
+ ]))
197
+ push(:worksheets)
198
+
199
+ # write the table container
200
+ worksheets_markup.template._Table
201
+ end
202
+
203
+ end
204
+
205
+
206
+
207
+ class Writer::Markup
208
+
209
+ attr_reader :template, :push_count, :pop_count
210
+
211
+ def initialize(opts={})
212
+ @markup = ""
213
+ @template = Undies::Template.new(Undies::Output.new(StringIO.new(@markup), opts))
214
+ @push_count = 0
215
+ @pop_count = 0
216
+ end
217
+
218
+ def push
219
+ @push_count += 1
220
+ @template.__push
221
+ end
222
+
223
+ def pop
224
+ @pop_count += 1
225
+ @template.__pop
226
+ end
227
+
228
+ def flush
229
+ while @push_count > @pop_count
230
+ pop
231
+ end
232
+ @template.__flush
233
+ self
234
+ end
235
+
236
+ def empty?; @markup.empty?; end
237
+
238
+ def to_s
239
+ @markup.to_s.strip
240
+ end
241
+
242
+ end
243
+
244
+ end
@@ -1,22 +1,45 @@
1
1
  require "assert"
2
+ require 'enumeration/assert_macros'
2
3
 
3
4
  require 'xmlss/element/cell'
4
5
 
5
6
  module Xmlss::Element
6
- class CellTest < Assert::Context
7
+
8
+ class CellTests < Assert::Context
9
+ include Enumeration::AssertMacros
10
+
7
11
  desc "Xmlss::Cell"
8
12
  before { @c = Cell.new }
9
13
  subject { @c }
10
14
 
11
15
  should be_styled
16
+ should have_class_method :writer
12
17
  should have_accessor :index, :formula, :href, :merge_across, :merge_down
13
18
  should have_reader :h_ref
14
19
 
20
+ should have_enum :type, {
21
+ :number => "Number",
22
+ :date_time => "DateTime",
23
+ :boolean => "Boolean",
24
+ :string => "String",
25
+ :error => "Error"
26
+ }
27
+
28
+ should have_accessor :data
29
+ should have_instance_method :data_xml_value
30
+
31
+ should "know its writer hook" do
32
+ assert_equal :cell, subject.class.writer
33
+ end
34
+
15
35
  should "set it's defaults" do
16
36
  assert_nil subject.formula
17
37
  assert_nil subject.href
18
38
  assert_nil subject.merge_across
19
39
  assert_nil subject.merge_down
40
+
41
+ assert_equal Cell.type(:string), subject.type
42
+ assert_equal "", subject.data
20
43
  end
21
44
 
22
45
  should "provide alias for :href" do
@@ -60,6 +83,42 @@ module Xmlss::Element
60
83
  end
61
84
  end
62
85
 
86
+ should "generate it's data xml value" do
87
+ assert_equal "12", Cell.new(12).data_xml_value
88
+ assert_equal "string", Cell.new(:data => "string").data_xml_value
89
+ assert_equal "2011-03-01T00:00:00", Cell.new(DateTime.parse('2011/03/01')).data_xml_value
90
+ assert_equal "2011-03-01T00:00:00", Cell.new(Date.parse('2011/03/01')).data_xml_value
91
+ time = Time.now
92
+ assert_equal time.strftime("%Y-%m-%dT%H:%M:%S"), Cell.new(time).data_xml_value
93
+ end
94
+
95
+ end
96
+
97
+ class ExplicitDataTest < CellTests
98
+ desc "when using explicit data type"
99
+ subject do
100
+ Cell.new(12, {:type => :string})
101
+ end
102
+
103
+ should "should ignore the data value's implied type" do
104
+ assert_equal Cell.type(:string), subject.type
105
+ end
106
+
107
+ end
108
+
109
+ class NoTypeDataTest < CellTests
110
+ desc "when no data type is specified"
111
+
112
+ should "cast types for Number, DateTime, Boolean, String" do
113
+ assert_equal Cell.type(:number), Cell.new(12).type
114
+ assert_equal Cell.type(:number), Cell.new(:data => 123.45).type
115
+ assert_equal Cell.type(:date_time), Cell.new(Time.now).type
116
+ assert_equal Cell.type(:boolean), Cell.new(true).type
117
+ assert_equal Cell.type(:boolean), Cell.new(false).type
118
+ assert_equal Cell.type(:string), Cell.new("a string").type
119
+ assert_equal Cell.type(:string), Cell.new(:data => :symbol).type
120
+ end
121
+
63
122
  end
64
123
 
65
124
  end
@@ -9,8 +9,13 @@ module Xmlss::Element
9
9
  subject { @c }
10
10
 
11
11
  should be_styled
12
+ should have_class_method :writer
12
13
  should have_accessor :width, :auto_fit_width, :hidden
13
14
 
15
+ should "know its writer hook" do
16
+ assert_equal :column, subject.class.writer
17
+ end
18
+
14
19
  should "set it's defaults" do
15
20
  assert_equal nil, subject.width
16
21
  assert_equal false, subject.auto_fit_width
@@ -9,8 +9,13 @@ module Xmlss::Element
9
9
  subject { @row }
10
10
 
11
11
  should be_styled
12
+ should have_class_method :writer
12
13
  should have_accessors :height, :auto_fit_height, :hidden
13
14
 
15
+ should "know its writer hook" do
16
+ assert_equal :row, subject.class.writer
17
+ end
18
+
14
19
  should "set it's defaults" do
15
20
  assert_nil subject.height
16
21
  assert_equal false, subject.auto_fit_height
@@ -8,8 +8,13 @@ module Xmlss::Element
8
8
  before { @wksht = Worksheet.new('sheet') }
9
9
  subject { @wksht }
10
10
 
11
+ should have_class_method :writer
11
12
  should have_accessor :name
12
13
 
14
+ should "know its writer hook" do
15
+ assert_equal :worksheet, subject.class.writer
16
+ end
17
+
13
18
  should "set it's defaults" do
14
19
  assert_equal 'sheet', subject.name
15
20
  end
@@ -29,12 +34,12 @@ module Xmlss::Element
29
34
  assert_equal "t[e]st test", ws.name
30
35
  end
31
36
 
32
- should "bark when no name is given" do
37
+ should "complain if given a name longer than 31 chars" do
33
38
  assert_raises ArgumentError do
34
- Worksheet.new(nil)
39
+ Worksheet.new('a'*32)
35
40
  end
36
- assert_raises ArgumentError do
37
- Worksheet.new("")
41
+ assert_nothing_raised do
42
+ Worksheet.new('a'*31)
38
43
  end
39
44
  end
40
45
 
@@ -0,0 +1,117 @@
1
+ require 'assert'
2
+
3
+ require 'xmlss/element_stack'
4
+ require 'xmlss/element/cell'
5
+ require 'xmlss/writer'
6
+
7
+ module Xmlss
8
+
9
+
10
+
11
+ class ElementStackTests < Assert::Context
12
+
13
+ class TestWriter
14
+ def initialize(io); @io = io; end
15
+ def write(element); @io << "|written: #{element.class.name} #{element.object_id}"; end
16
+ def push(template); @io << "|#{template}:opened"; end
17
+ def pop(template); @io << "|#{template}:closed"; end
18
+ end
19
+
20
+ desc "an element stack"
21
+ before do
22
+ @cell1 = Element::Cell.new("1")
23
+ @cell2 = Element::Cell.new("2")
24
+ @es = ElementStack.new(TestWriter.new(@test_io = ''), 'tests')
25
+ end
26
+ subject { @es }
27
+
28
+ should have_reader :markup_type
29
+ should have_instance_methods :current, :size, :level, :empty?, :first, :last
30
+ should have_instance_methods :push, :pop, :using
31
+
32
+ should "be empty by default" do
33
+ assert_empty subject
34
+ end
35
+
36
+ should "know the writer markup_type its driving" do
37
+ assert_equal 'tests', subject.markup_type
38
+ end
39
+
40
+ end
41
+
42
+
43
+
44
+ class StackTests < ElementStackTests
45
+
46
+ should "push elements onto the stack" do
47
+ assert_nothing_raised do
48
+ subject.push(@cell1)
49
+ end
50
+
51
+ assert_equal 1, subject.size
52
+ end
53
+
54
+ should "know its level (should be one less than the array's size)" do
55
+ assert_equal 0, subject.level
56
+ subject.push(@cell1)
57
+ assert_equal 1, subject.level
58
+ end
59
+
60
+ should "fetch the last item in the array with the current method" do
61
+ subject.push(@cell1)
62
+ subject.push(@cell2)
63
+
64
+ assert_same @cell2, subject.current
65
+ end
66
+
67
+ should "remove the last item in the array with the pop method" do
68
+ subject.push(@cell1)
69
+ subject.push(@cell2)
70
+
71
+ assert_equal 2, subject.size
72
+
73
+ elem = subject.pop
74
+ assert_same @cell2, elem
75
+ assert_equal 1, subject.size
76
+ end
77
+
78
+ end
79
+
80
+
81
+
82
+ class WriterTests < ElementStackTests
83
+ should "open the current element element when a new element is pushed" do
84
+ expected = "|written: Xmlss::Element::Cell #{@cell1.object_id}"
85
+ expected += "|#{subject.markup_type}:opened"
86
+ subject.push(@cell1)
87
+ subject.push(@cell2)
88
+
89
+ assert_equal expected, @test_io
90
+ end
91
+
92
+ should "write the current element if no child element is pushed" do
93
+ subject.push(@cell1)
94
+ subject.push(@cell2)
95
+ subject.pop
96
+
97
+ assert_match /\|written: Xmlss::Element::Cell #{@cell2.object_id}$/, @test_io
98
+ end
99
+
100
+ should "close an element when its popped" do
101
+ expected = "|written: Xmlss::Element::Cell #{@cell1.object_id}"
102
+ expected += "|#{subject.markup_type}:opened"
103
+ expected += "|written: Xmlss::Element::Cell #{@cell2.object_id}"
104
+ expected += "|#{subject.markup_type}:closed"
105
+ subject.push(@cell1)
106
+ subject.push(@cell2)
107
+ subject.pop
108
+ subject.pop
109
+
110
+ assert_equal expected, @test_io
111
+ end
112
+
113
+ end
114
+
115
+
116
+
117
+ end
@@ -25,9 +25,14 @@ module Xmlss::Style
25
25
  :bottom => "Bottom"
26
26
  }
27
27
 
28
+ should have_class_method :writer
28
29
  should have_accessors :wrap_text, :rotate
29
30
  should have_instance_methods :wrap_text?
30
31
 
32
+ should "know its writer" do
33
+ assert_equal :alignment, subject.class.writer
34
+ end
35
+
31
36
  should "set it's defaults" do
32
37
  assert_equal false, subject.wrap_text
33
38
  assert_equal nil, subject.horizontal
@@ -8,8 +8,13 @@ module Xmlss::Style
8
8
  before { @bs = Base.new(:test) }
9
9
  subject { @bs }
10
10
 
11
+ should have_class_method :writer
11
12
  should have_reader :id, :i_d
12
13
 
14
+ should "know its writer" do
15
+ assert_equal :style, subject.class.writer
16
+ end
17
+
13
18
  should "bark if you don't init with an id" do
14
19
  assert_raises ArgumentError do
15
20
  Base.new(nil)
@@ -4,7 +4,10 @@ require 'enumeration/assert_macros'
4
4
  require 'xmlss/style/border'
5
5
 
6
6
  module Xmlss::Style
7
- class BorderTest < Assert::Context
7
+
8
+
9
+
10
+ class BorderTests < Assert::Context
8
11
  include Enumeration::AssertMacros
9
12
 
10
13
  desc "Xmlss::Style::Border"
@@ -36,9 +39,13 @@ module Xmlss::Style
36
39
  :dash_dot_dot => "DashDotDot"
37
40
  }
38
41
 
39
-
42
+ should have_class_method :writer
40
43
  should have_accessors :color
41
44
 
45
+ should "know its writer" do
46
+ assert_equal :border, subject.class.writer
47
+ end
48
+
42
49
  should "set it's defaults" do
43
50
  assert_equal nil, subject.color
44
51
  assert_equal nil, subject.position
@@ -85,4 +92,21 @@ module Xmlss::Style
85
92
 
86
93
  end
87
94
 
95
+
96
+
97
+ class BordersTests < Assert::Context
98
+ desc "Xmlss::Style::Borders"
99
+ before { @bs = Xmlss::Style::Borders.new }
100
+ subject { @bs }
101
+
102
+ should have_class_method :writer
103
+
104
+ should "know its writer" do
105
+ assert_equal :borders, subject.class.writer
106
+ end
107
+
108
+ end
109
+
110
+
111
+
88
112
  end
@@ -23,11 +23,16 @@ module Xmlss::Style
23
23
  :superscript => 'Superscript'
24
24
  }
25
25
 
26
+ should have_class_method :writer
26
27
  should have_reader :vertical_align
27
28
  should have_accessors :bold, :color, :italic, :size, :strike_through
28
29
  should have_accessors :shadow, :underline, :alignment, :name
29
30
  should have_instance_methods :bold?, :italic?, :strike_through?, :shadow?
30
31
 
32
+ should "know its writer" do
33
+ assert_equal :font, subject.class.writer
34
+ end
35
+
31
36
  should "set it's defaults" do
32
37
  assert_equal false, subject.bold
33
38
  assert_equal nil, subject.color
@@ -34,8 +34,13 @@ module Xmlss::Style
34
34
  :thin_diag_cross => "ThinDiagCross"
35
35
  }
36
36
 
37
+ should have_class_method :writer
37
38
  should have_accessor :color, :pattern_color
38
39
 
40
+ should "know its writer" do
41
+ assert_equal :interior, subject.class.writer
42
+ end
43
+
39
44
  should "set it's defaults" do
40
45
  assert_equal nil, subject.color
41
46
  assert_equal nil, subject.pattern
@@ -11,8 +11,13 @@ module Xmlss::Style
11
11
  before { @nf = NumberFormat.new }
12
12
  subject { @nf }
13
13
 
14
+ should have_class_method :writer
14
15
  should have_accessor :format
15
16
 
17
+ should "know its writer" do
18
+ assert_equal :number_format, subject.class.writer
19
+ end
20
+
16
21
  should "set attributes at init" do
17
22
  nf = NumberFormat.new("General")
18
23
  assert_equal "General", nf.format
@@ -9,8 +9,13 @@ module Xmlss::Style
9
9
  before { @sp = Xmlss::Style::Protection.new }
10
10
  subject { @sp }
11
11
 
12
- should have_instance_methods :protected?
12
+ should have_class_method :writer
13
13
  should have_accessor :protect
14
+ should have_instance_methods :protected?
15
+
16
+ should "know its writer" do
17
+ assert_equal :protection, subject.class.writer
18
+ end
14
19
 
15
20
  should "set it's defaults" do
16
21
  assert_equal false, subject.protected?