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
data/Gemfile.lock CHANGED
@@ -1,9 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- xmlss (0.4.1)
4
+ xmlss (1.0.0.rc.1)
5
5
  enumeration (~> 1.3)
6
- undies (~> 2.2)
6
+ undies (~> 2.2.1)
7
7
 
8
8
  GEM
9
9
  remote: http://rubygems.org/
@@ -17,7 +17,7 @@ GEM
17
17
  enumeration (1.3.1)
18
18
  rake (0.9.2)
19
19
  ruby-prof (0.10.8)
20
- undies (2.2.0)
20
+ undies (2.2.1)
21
21
 
22
22
  PLATFORMS
23
23
  ruby
data/README.rdoc CHANGED
@@ -11,7 +11,7 @@ A ruby DSL for generating spreadsheets in the XML Spreadsheet format. It provid
11
11
 
12
12
  require 'xmlss'
13
13
 
14
- workbook = Xmlss::Worksheet.new do
14
+ workbook = Xmlss::Workbook.new(Xmlss::Writer.new) do
15
15
 
16
16
  worksheet("5 columns, 1 row") {
17
17
  5.times do
@@ -114,7 +114,7 @@ To generate a spreadsheet, create an Xmlss::Workbook instance and build the work
114
114
 
115
115
  Xmlss evals the build proc in the scope of the workbook instance. This means that the build has access to only the data it is given or the DSL itself. Data is given in the form of a Hash. The string form of the hash keys are exposed as local workbook methods that return their corresponding values.
116
116
 
117
- Xmlss::Workbook.new(:data => {:sheet_name => 'A cool sheet'}) do
117
+ Xmlss::Workbook.new(Xmlss::Writer.new, :sheet_name => 'A cool sheet') do
118
118
  worksheet(sheet_name) {
119
119
  ...
120
120
  }
@@ -124,7 +124,7 @@ Xmlss evals the build proc in the scope of the workbook instance. This means th
124
124
 
125
125
  Xmlss uses Undies (https://github.com/kelredd/undies) to render the XML output. The :output Workbook option is used to create Undies::Ouput objects. See their docs for usage details (https://github.com/kelredd/undies/blob/master/README.rdoc).
126
126
 
127
- Xmlss::Workbook.new(:output => {:pp => 2}) do
127
+ Xmlss::Workbook.new(Xmlss::Writer.new(:pp => 2)) do
128
128
  worksheet(sheet_name) {
129
129
  ...
130
130
  }
@@ -137,7 +137,7 @@ The above examples all pass in a build proc that is eval'd in the scope of the w
137
137
  To render using this approach, create a Workbook instance passing it data and output info as above. However, don't pass any build proc and save off the created workbook:
138
138
 
139
139
  # choosing not to use any local data or output options
140
- workbook = Xmlss::Workbook.new
140
+ workbook = Xmlss::Workbook.new(Xmlss::Writer.new)
141
141
 
142
142
  Now just interact with the Workbook API directly.
143
143
 
@@ -26,7 +26,7 @@ class XmlssProfilerRunner
26
26
  end
27
27
 
28
28
  @result = RubyProf.profile do
29
- Xmlss::Workbook.new(:output => {:pp => 2}, &build).to_file("./bench/profiler_#{n}.xml")
29
+ Xmlss::Workbook.new(Xmlss::Writer.new(:pp => 2), &build).to_file("./bench/profiler_#{n}.xml")
30
30
  end
31
31
 
32
32
  end
@@ -5,7 +5,7 @@ class ExampleWorkbook < Xmlss::Workbook
5
5
  def initialize(name, &build)
6
6
  puts "Building #{name} workbook xml..."
7
7
 
8
- super(:output => {:pp => 2}, &build)
8
+ super(Xmlss::Writer.new(:pp => 2), &build)
9
9
  self.to_file("./examples/#{name}.xml")
10
10
 
11
11
  puts "... ready - open in Excel or whatever..."
data/examples/simple.rb CHANGED
@@ -16,10 +16,27 @@ ExampleWorkbook.new("simple") do
16
16
 
17
17
  # put data into the row (infer type)
18
18
  [1, "text", 123.45, "<>&'\"/", "$45.23"].each do |data_value|
19
- cell { data data_value }
19
+ cell(data_value)
20
20
  end
21
21
 
22
22
  }
23
23
  }
24
24
 
25
+ worksheet('2 rows, 2 columns') {
26
+ 2.times do
27
+ column
28
+ end
29
+
30
+ 2.times do
31
+ row {
32
+
33
+ # put data into the row (infer type)
34
+ [1, 2].each do |data_value|
35
+ cell { data data_value }
36
+ end
37
+
38
+ }
39
+ end
40
+ }
41
+
25
42
  end
data/examples/simple.xml CHANGED
@@ -29,4 +29,26 @@
29
29
  </Row>
30
30
  </Table>
31
31
  </Worksheet>
32
+ <Worksheet ss:Name="2 rows, 2 columns">
33
+ <Table>
34
+ <Column />
35
+ <Column />
36
+ <Row>
37
+ <Cell>
38
+ <Data ss:Type="Number">1</Data>
39
+ </Cell>
40
+ <Cell>
41
+ <Data ss:Type="Number">2</Data>
42
+ </Cell>
43
+ </Row>
44
+ <Row>
45
+ <Cell>
46
+ <Data ss:Type="Number">1</Data>
47
+ </Cell>
48
+ <Cell>
49
+ <Data ss:Type="Number">2</Data>
50
+ </Cell>
51
+ </Row>
52
+ </Table>
53
+ </Worksheet>
32
54
  </Workbook>
@@ -1,12 +1,32 @@
1
+ require 'enumeration'
2
+
1
3
  module Xmlss; end
2
4
  module Xmlss::Element
3
5
  class Cell
4
6
 
7
+ def self.writer; :cell; end
8
+
5
9
  attr_accessor :index, :style_id, :formula, :href, :merge_across, :merge_down
6
10
  alias_method :style_i_d, :style_id
7
11
  alias_method :h_ref, :href
8
12
 
9
- def initialize(attrs={}, &build)
13
+ attr_accessor :data
14
+
15
+ include Enumeration
16
+ enum :type, {
17
+ :number => "Number",
18
+ :date_time => "DateTime",
19
+ :boolean => "Boolean",
20
+ :string => "String",
21
+ :error => "Error"
22
+ }
23
+
24
+ def initialize(*args, &build)
25
+ attrs = args.last.kind_of?(::Hash) ? args.pop : {}
26
+
27
+ self.data = args.last.nil? ? (attrs[:data] || "") : args.last
28
+ self.type = attrs[:type] unless attrs[:type].nil?
29
+
10
30
  self.index = attrs[:index]
11
31
  self.style_id = attrs[:style_id]
12
32
  self.formula = attrs[:formula]
@@ -15,6 +35,20 @@ module Xmlss::Element
15
35
  self.merge_down = attrs[:merge_down] || 0
16
36
  end
17
37
 
38
+ def data=(v)
39
+ self.type = data_type(v)
40
+ @data = v
41
+ end
42
+
43
+ def data_xml_value
44
+ case self.data
45
+ when ::Date, ::Time, ::DateTime
46
+ self.data.strftime("%Y-%m-%dT%H:%M:%S")
47
+ else
48
+ self.data.to_s
49
+ end
50
+ end
51
+
18
52
  [:index, :merge_across, :merge_down].each do |meth|
19
53
  define_method("#{meth}=") do |value|
20
54
  if value && !value.kind_of?(::Fixnum)
@@ -24,5 +58,22 @@ module Xmlss::Element
24
58
  end
25
59
  end
26
60
 
61
+ private
62
+
63
+ def data_type(v)
64
+ case v
65
+ when ::Numeric
66
+ :number
67
+ when ::Date, ::Time
68
+ :date_time
69
+ when ::TrueClass, ::FalseClass
70
+ :boolean
71
+ when ::String, ::Symbol
72
+ :string
73
+ else
74
+ :string
75
+ end
76
+ end
77
+
27
78
  end
28
79
  end
@@ -2,6 +2,8 @@ module Xmlss; end
2
2
  module Xmlss::Element
3
3
  class Column
4
4
 
5
+ def self.writer; :column; end
6
+
5
7
  attr_accessor :style_id, :width, :auto_fit_width, :hidden
6
8
  alias_method :style_i_d, :style_id
7
9
 
@@ -2,6 +2,8 @@ module Xmlss; end
2
2
  module Xmlss::Element
3
3
  class Row
4
4
 
5
+ def self.writer; :row; end
6
+
5
7
  attr_accessor :style_id, :height, :auto_fit_height, :hidden
6
8
  alias_method :style_i_d, :style_id
7
9
 
@@ -1,23 +1,31 @@
1
1
  require 'xmlss/element/column'
2
2
  require 'xmlss/element/row'
3
3
  require 'xmlss/element/cell'
4
- require 'xmlss/element/data'
5
4
 
6
5
  module Xmlss; end
7
6
  module Xmlss::Element
8
7
  class Worksheet
9
8
 
9
+ def self.writer; :worksheet; end
10
+
10
11
  attr_accessor :name
11
12
 
12
- def initialize(name, attrs={})
13
- self.name = name
13
+ def initialize(*args)
14
+ attrs, self.name = [
15
+ args.last.kind_of?(::Hash) ? args.pop : {},
16
+ args.last
17
+ ]
14
18
  end
15
19
 
16
20
  def name=(value)
17
- if value.nil? || value.to_s.empty?
18
- raise ArgumentError, "'#{name.inspect}' is not a good name for a worksheet"
21
+ if value.to_s.length > 31
22
+ raise ArgumentError, "worksheet names must be less than 32 characters long"
23
+ end
24
+ @name = if !value.nil? && !value.to_s.empty?
25
+ sanitized_name(value.to_s)
26
+ else
27
+ "" # TODO: make sure you don't write a worksheet with no sanitized_name
19
28
  end
20
- @name = sanitized_name(value.to_s)
21
29
  end
22
30
 
23
31
  private
@@ -0,0 +1,69 @@
1
+ module Xmlss
2
+
3
+ class ElementStack
4
+
5
+ # this class is just a wrapper to Array. I want to treat this as a
6
+ # stack of objects for the workbook DSL to reference. I need to push
7
+ # an object onto the stack, reference it using the 'current' method,
8
+ # and pop it off the stack when I'm done.
9
+
10
+ attr_reader :markup_type
11
+
12
+ def initialize(writer, markup_type)
13
+ @stack = []
14
+ @writer = writer
15
+ @markup_type = markup_type
16
+ @written_level = 0
17
+ end
18
+
19
+ def empty?; @stack.empty?; end
20
+ def size; @stack.size; end
21
+ def first; @stack.first; end
22
+ def last; @stack.last; end
23
+
24
+ alias_method :current, :last
25
+ alias_method :level, :size
26
+
27
+ def push(element)
28
+ if @written_level < level
29
+ open(current)
30
+ end
31
+ @stack.push(element)
32
+ end
33
+
34
+ def pop
35
+ if !empty?
36
+ if @written_level < level
37
+ @stack.pop.tap { |elem| write(elem) }
38
+ else
39
+ @stack.pop.tap { |elem| close(elem) }
40
+ end
41
+ end
42
+ end
43
+
44
+ def using(element, &block)
45
+ push(element)
46
+ (block || Proc.new {}).call
47
+ pop
48
+ end
49
+
50
+ private
51
+
52
+ def open(element)
53
+ write(element)
54
+ @writer.push(@markup_type)
55
+ @written_level += 1
56
+ end
57
+
58
+ def close(element)
59
+ @writer.pop(@markup_type)
60
+ @written_level -= 1
61
+ end
62
+
63
+ def write(element)
64
+ @writer.write(element)
65
+ end
66
+
67
+ end
68
+
69
+ end
@@ -3,6 +3,8 @@ require 'xmlss/style/base'
3
3
  module Xmlss::Style
4
4
  class Alignment
5
5
 
6
+ def self.writer; :alignment; end
7
+
6
8
  include Enumeration
7
9
  enum :horizontal, {
8
10
  :automatic => "Automatic",
@@ -12,6 +12,8 @@ require 'xmlss/style/protection'
12
12
  module Xmlss::Style
13
13
  class Base
14
14
 
15
+ def self.writer; :style; end
16
+
15
17
  attr_reader :id
16
18
  alias_method :i_d, :id
17
19
 
@@ -1,8 +1,11 @@
1
1
  require 'xmlss/style/base'
2
2
 
3
3
  module Xmlss::Style
4
+
4
5
  class Border
5
6
 
7
+ def self.writer; :border; end
8
+
6
9
  include Enumeration
7
10
 
8
11
  enum :position, {
@@ -40,4 +43,11 @@ module Xmlss::Style
40
43
  end
41
44
 
42
45
  end
46
+
47
+ class Borders
48
+
49
+ def self.writer; :borders; end
50
+
51
+ end
52
+
43
53
  end
@@ -3,6 +3,8 @@ require 'xmlss/style/base'
3
3
  module Xmlss::Style
4
4
  class Font
5
5
 
6
+ def self.writer; :font; end
7
+
6
8
  include Enumeration
7
9
  enum :underline, {
8
10
  :single => 'Single',
@@ -3,6 +3,8 @@ require 'xmlss/style/base'
3
3
  module Xmlss::Style
4
4
  class Interior
5
5
 
6
+ def self.writer; :interior; end
7
+
6
8
  include Enumeration
7
9
  enum :pattern, {
8
10
  :none => "None",
@@ -3,6 +3,8 @@ require 'xmlss/style/base'
3
3
  module Xmlss::Style
4
4
  class NumberFormat
5
5
 
6
+ def self.writer; :number_format; end
7
+
6
8
  attr_accessor :format
7
9
 
8
10
  def initialize(format=nil)
@@ -3,6 +3,8 @@ require 'xmlss/style/base'
3
3
  module Xmlss::Style
4
4
  class Protection
5
5
 
6
+ def self.writer; :protection; end
7
+
6
8
  attr_accessor :protect
7
9
 
8
10
  def initialize(value=nil)
data/lib/xmlss/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Xmlss
2
- VERSION = "0.4.1"
2
+ VERSION = "1.0.0.rc.1"
3
3
  end
@@ -1,16 +1,27 @@
1
- require 'xmlss/undies_writer'
1
+ require 'xmlss/writer'
2
+ require 'xmlss/element_stack'
2
3
  require 'xmlss/style/base'
3
4
  require 'xmlss/element/worksheet'
4
5
 
5
6
  module Xmlss
6
7
  class Workbook
7
8
 
8
- # TODO: (writer, data={}, &build)
9
- def initialize(opts={}, &build)
9
+ def self.writer(workbook)
10
+ workbook.instance_variable_get("@__xmlss_writer")
11
+ end
12
+
13
+ def self.styles_stack(workbook)
14
+ workbook.instance_variable_get("@__xmlss_styles_stack")
15
+ end
16
+
17
+ def self.worksheets_stack(workbook)
18
+ workbook.instance_variable_get("@__xmlss_worksheets_stack")
19
+ end
20
+
21
+ def initialize(writer, data={}, &build)
10
22
  # (don't pollute workbook scope that the build may run in)
11
23
 
12
24
  # apply :data options to workbook scope
13
- data = (opts || {})[:data] || {}
14
25
  if (data.keys.map(&:to_s) & self.public_methods.map(&:to_s)).size > 0
15
26
  raise ArgumentError, "data conflicts with workbook public methods."
16
27
  end
@@ -18,14 +29,16 @@ module Xmlss
18
29
  data.each {|key, value| metaclass.class_eval { define_method(key){value} }}
19
30
 
20
31
  # setup the Undies xml writer with any :output options
21
- @__xmlss_undies_writer = UndiesWriter.new((opts || {})[:output] || {})
32
+ @__xmlss_writer = writer
33
+ @__xmlss_styles_stack = ElementStack.new(writer, :styles)
34
+ @__xmlss_worksheets_stack = ElementStack.new(writer, :worksheets)
22
35
 
23
36
  # run any instance workbook build given
24
37
  instance_eval(&build) if build
25
38
  end
26
39
 
27
40
  def to_s
28
- @__xmlss_undies_writer.workbook
41
+ self.class.writer(self).workbook
29
42
  end
30
43
 
31
44
  def to_file(path)
@@ -34,85 +47,89 @@ module Xmlss
34
47
  File.exists?(path) ? path : false
35
48
  end
36
49
 
37
- # Workbook elements API
50
+ # Workbook styles API
38
51
 
39
- def worksheet(*args, &block)
40
- Element::Worksheet.new(*args).tap do |elem|
41
- @__xmlss_undies_writer.worksheet(elem, &block)
42
- end
52
+ def style(*args, &block)
53
+ self.class.styles_stack(self).using(Style::Base.new(*args), &block)
43
54
  end
44
55
 
45
- def column(*args, &block)
46
- Element::Column.new(*args).tap do |elem|
47
- @__xmlss_undies_writer.column(elem, &block)
48
- end
56
+ def alignment(*args, &block)
57
+ self.class.styles_stack(self).using(Style::Alignment.new(*args), &block)
49
58
  end
50
59
 
51
- def row(*args, &block)
52
- Element::Row.new(*args).tap do |elem|
53
- @__xmlss_undies_writer.row(elem, &block)
54
- end
60
+ def borders(*args, &block)
61
+ self.class.styles_stack(self).using(Style::Borders.new(*args), &block)
55
62
  end
56
63
 
57
- def cell(*args, &block)
58
- Element::Cell.new(*args).tap do |elem|
59
- @__xmlss_undies_writer.cell(elem, &block)
60
- end
64
+ def border(*args, &block)
65
+ self.class.styles_stack(self).using(Style::Border.new(*args), &block)
61
66
  end
62
67
 
63
- def data(*args, &block)
64
- Element::Data.new(*args).tap do |elem|
65
- @__xmlss_undies_writer.data(elem, &block)
66
- end
68
+ def font(*args, &block)
69
+ self.class.styles_stack(self).using(Style::Font.new(*args), &block)
67
70
  end
68
71
 
69
- # Workbook styles API
70
-
71
- def style(*args, &block)
72
- Style::Base.new(*args).tap do |style|
73
- @__xmlss_undies_writer.style(style, &block)
74
- end
72
+ def interior(*args, &block)
73
+ self.class.styles_stack(self).using(Style::Interior.new(*args), &block)
75
74
  end
76
75
 
77
- def alignment(*args, &block)
78
- Style::Alignment.new(*args).tap do |style|
79
- @__xmlss_undies_writer.alignment(style, &block)
80
- end
76
+ def number_format(*args, &block)
77
+ self.class.styles_stack(self).using(Style::NumberFormat.new(*args), &block)
81
78
  end
82
79
 
83
- def borders(*args, &block)
84
- @__xmlss_undies_writer.borders(&block)
80
+ def protection(*args, &block)
81
+ self.class.styles_stack(self).using(Style::Protection.new(*args), &block)
85
82
  end
86
83
 
87
- def border(*args, &block)
88
- Style::Border.new(*args).tap do |style|
89
- @__xmlss_undies_writer.border(style, &block)
90
- end
84
+ # Workbook elements API
85
+
86
+ def worksheet(*args, &block)
87
+ self.class.worksheets_stack(self).using(Element::Worksheet.new(*args), &block)
91
88
  end
92
89
 
93
- def font(*args, &block)
94
- Style::Font.new(*args).tap do |style|
95
- @__xmlss_undies_writer.font(style, &block)
96
- end
90
+ def column(*args, &block)
91
+ self.class.worksheets_stack(self).using(Element::Column.new(*args), &block)
97
92
  end
98
93
 
99
- def interior(*args, &block)
100
- Style::Interior.new(*args).tap do |style|
101
- @__xmlss_undies_writer.interior(style, &block)
102
- end
94
+ def row(*args, &block)
95
+ self.class.worksheets_stack(self).using(Element::Row.new(*args), &block)
103
96
  end
104
97
 
105
- def number_format(*args, &block)
106
- Style::NumberFormat.new(*args).tap do |style|
107
- @__xmlss_undies_writer.number_format(style, &block)
98
+ def cell(*args, &block)
99
+ self.class.worksheets_stack(self).using(Element::Cell.new(*args), &block)
100
+ end
101
+
102
+ # Workbook element attributes API
103
+
104
+ [ :data, # cell
105
+ :type, # cell
106
+ :index, # cell
107
+ :style_id, # cell, row, :column
108
+ :formula, # cell
109
+ :href, # cell
110
+ :merge_across, # cell
111
+ :merge_down, # cell
112
+ :height, # row
113
+ :auto_fit_height, # row
114
+ :hidden, # row, column
115
+ :width, # column
116
+ :auto_fit_width, # column
117
+ :name # worksheet
118
+ ].each do |a|
119
+ define_method(a) do |value|
120
+ if (current_element = self.class.worksheets_stack(self).current)
121
+ current_element.send("#{a}=", value)
122
+ end
108
123
  end
109
124
  end
110
125
 
111
- def protection(*args, &block)
112
- Style::Protection.new(*args).tap do |style|
113
- @__xmlss_undies_writer.protection(style, &block)
114
- end
126
+ # overriding to make less noisy
127
+ def to_str(*args)
128
+ "#<Xmlss::Workbook:#{self.object_id} " +
129
+ "current_element=#{self.class.worksheets_stack(self).current.inspect}, " +
130
+ "current_style=#{self.class.styles_stack(self).current.inspect}>"
115
131
  end
132
+ alias_method :inspect, :to_str
116
133
 
117
134
  end
118
135
  end