xmlss 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/.gitignore +1 -2
  2. data/Gemfile +1 -0
  3. data/Gemfile.lock +12 -11
  4. data/README.rdoc +141 -107
  5. data/Rakefile +24 -1
  6. data/bench/profiler.rb +6 -0
  7. data/bench/profiler_runner.rb +43 -0
  8. data/examples/example_workbook.rb +8 -12
  9. data/examples/layout.rb +34 -60
  10. data/examples/layout.xml +66 -0
  11. data/examples/simple.rb +16 -21
  12. data/examples/simple.xml +32 -0
  13. data/examples/styles.rb +52 -56
  14. data/examples/styles.xml +50 -0
  15. data/examples/text.rb +18 -28
  16. data/examples/text.xml +20 -0
  17. data/lib/xmlss/{cell.rb → element/cell.rb} +4 -14
  18. data/lib/xmlss/{column.rb → element/column.rb} +2 -7
  19. data/lib/xmlss/{data.rb → element/data.rb} +7 -12
  20. data/lib/xmlss/{row.rb → element/row.rb} +2 -13
  21. data/lib/xmlss/{worksheet.rb → element/worksheet.rb} +7 -18
  22. data/lib/xmlss/style/alignment.rb +3 -10
  23. data/lib/xmlss/style/base.rb +6 -45
  24. data/lib/xmlss/style/border.rb +5 -5
  25. data/lib/xmlss/style/font.rb +2 -9
  26. data/lib/xmlss/style/interior.rb +2 -5
  27. data/lib/xmlss/style/number_format.rb +4 -7
  28. data/lib/xmlss/style/protection.rb +4 -7
  29. data/lib/xmlss/undies_writer.rb +172 -0
  30. data/lib/xmlss/version.rb +1 -1
  31. data/lib/xmlss/workbook.rb +97 -31
  32. data/lib/xmlss.rb +1 -28
  33. data/test/{cell_test.rb → element/cell_test.rb} +11 -35
  34. data/test/{column_test.rb → element/column_test.rb} +6 -12
  35. data/test/{data_test.rb → element/data_test.rb} +15 -10
  36. data/test/{row_test.rb → element/row_test.rb} +5 -35
  37. data/test/{worksheet_test.rb → element/worksheet_test.rb} +4 -30
  38. data/test/helper.rb +13 -30
  39. data/test/style/alignment_test.rb +13 -45
  40. data/test/style/base_test.rb +1 -106
  41. data/test/style/border_test.rb +12 -32
  42. data/test/style/font_test.rb +11 -43
  43. data/test/style/interior_test.rb +7 -27
  44. data/test/style/number_format_test.rb +5 -21
  45. data/test/style/protection_test.rb +2 -12
  46. data/test/undies_writer_test.rb +333 -0
  47. data/test/workbook_test.rb +89 -44
  48. data/xmlss.gemspec +2 -2
  49. metadata +37 -39
  50. data/lib/xmlss/item_set.rb +0 -17
  51. data/lib/xmlss/table.rb +0 -22
  52. data/lib/xmlss/xml.rb +0 -60
  53. data/test/item_set_test.rb +0 -27
  54. data/test/table_test.rb +0 -56
  55. data/test/xml_test.rb +0 -81
  56. data/test/xmlss_test.rb +0 -31
@@ -0,0 +1,172 @@
1
+ require 'undies'
2
+ require 'stringio'
3
+
4
+ module Xmlss
5
+ class UndiesWriter
6
+
7
+ XML_NS = "xmlns"
8
+ SHEET_NS = "ss"
9
+ NS_URI = "urn:schemas-microsoft-com:office:spreadsheet"
10
+
11
+ # The Undies writer is responsible for driving the Undies API to generate
12
+ # the xmlss xml markup for the workbook.
13
+ # Because order doesn't matter when defining style and worksheet elements,
14
+ # the writer has to buffer the style and worksheet markup separately,
15
+ # then put them together according to Xmlss spec to build the final
16
+ # workbook markup.
17
+
18
+ def self.attributes(thing, *attrs)
19
+ [*attrs].flatten.inject({}) do |xattrs, a|
20
+ xattrs.merge(if !(xv = self.coerce(thing.send(a))).nil?
21
+ {"#{SHEET_NS}:#{self.classify(a)}" => xv.to_s}
22
+ else
23
+ {}
24
+ end)
25
+ end
26
+ end
27
+
28
+ def self.classify(underscored_string)
29
+ underscored_string.
30
+ to_s.downcase.
31
+ split("_").
32
+ collect{|part| part.capitalize}.
33
+ join('')
34
+ end
35
+
36
+ def self.coerce(value)
37
+ if value == true
38
+ 1
39
+ elsif ["",false].include?(value)
40
+ # don't include false or empty string values
41
+ nil
42
+ else
43
+ value
44
+ end
45
+ end
46
+
47
+ attr_reader :style_markup, :element_markup
48
+
49
+ def initialize(output_opts={})
50
+ @opts = output_opts || {}
51
+
52
+ # buffer style markup and create a template to write to it
53
+ @style_markup = ""
54
+ styles_out = Undies::Output.new(StringIO.new(@style_markup), @opts.merge({
55
+ :pp_level => 2
56
+ }))
57
+ @styles_t = Undies::Template.new(styles_out)
58
+
59
+ # buffer worksheet markup and create a template to write to it
60
+ @element_markup = ""
61
+ worksheets_out = Undies::Output.new(StringIO.new(@element_markup), @opts.merge({
62
+ :pp_level => 1
63
+ }))
64
+ @worksheets_t = Undies::Template.new(worksheets_out)
65
+ end
66
+
67
+ def flush
68
+ Undies::Template.flush(@worksheets_t)
69
+ Undies::Template.flush(@styles_t)
70
+ self
71
+ end
72
+
73
+ # return the full workbook markup, combining the buffers to xmlss spec
74
+ def workbook
75
+ self.flush
76
+ "".tap do |markup|
77
+ Undies::Template.new(Undies::Source.new(Proc.new do
78
+ __ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
79
+ _Workbook(XML_NS => NS_URI, "#{XML_NS}:#{SHEET_NS}" => NS_URI) {
80
+ _Styles {
81
+ ___ style_markup.to_s.strip
82
+ }
83
+ __ element_markup.to_s.strip
84
+ }
85
+ end), {
86
+ :style_markup => @style_markup,
87
+ :element_markup => @element_markup
88
+ }, Undies::Output.new(StringIO.new(markup), @opts))
89
+ end.strip
90
+ end
91
+
92
+ # workbook style markup directives
93
+
94
+ def alignment(alignment, &block)
95
+ @styles_t._Alignment(self.class.attributes(alignment, [
96
+ :horizontal, :vertical, :wrap_text, :rotate
97
+ ]))
98
+ end
99
+
100
+ def border(border, &block)
101
+ @styles_t._Border(self.class.attributes(border, [
102
+ :color, :position, :weight, :line_style
103
+ ]))
104
+ end
105
+
106
+ def borders(&block)
107
+ @styles_t._Borders(&block)
108
+ end
109
+
110
+ def font(font, &block)
111
+ @styles_t._Font(self.class.attributes(font, [
112
+ :bold, :color, :italic, :size, :shadow, :font_name,
113
+ :strike_through, :underline, :vertical_align
114
+ ]))
115
+ end
116
+
117
+ def interior(interior, &block)
118
+ @styles_t._Interior(self.class.attributes(interior, [
119
+ :color, :pattern, :pattern_color
120
+ ]))
121
+ end
122
+
123
+ def number_format(number_format, &block)
124
+ @styles_t._NumberFormat(self.class.attributes(number_format, [
125
+ :format
126
+ ]))
127
+ end
128
+
129
+ def protection(protection, &block)
130
+ @styles_t._Protection(self.class.attributes(protection, [
131
+ :protect
132
+ ]))
133
+ end
134
+
135
+ def style(style, &block)
136
+ @styles_t._Style(self.class.attributes(style, :i_d), &block)
137
+ end
138
+
139
+ # workbook element markup directives
140
+
141
+ def data(data, &block)
142
+ @worksheets_t._Data(self.class.attributes(data, :type)) {
143
+ @worksheets_t.__ data.xml_value
144
+ }
145
+ end
146
+
147
+ def cell(cell, &block)
148
+ @worksheets_t._Cell(self.class.attributes(cell, [
149
+ :index, :style_i_d, :formula, :h_ref, :merge_across, :merge_down
150
+ ]), &block)
151
+ end
152
+
153
+ def row(row, &block)
154
+ @worksheets_t._Row(self.class.attributes(row, [
155
+ :style_i_d, :height, :auto_fit_height, :hidden
156
+ ]), &block)
157
+ end
158
+
159
+ def column(column, &block)
160
+ @worksheets_t._Column(self.class.attributes(column, [
161
+ :style_i_d, :width, :auto_fit_width, :hidden
162
+ ]))
163
+ end
164
+
165
+ def worksheet(worksheet, &block)
166
+ @worksheets_t._Worksheet(self.class.attributes(worksheet, :name)) {
167
+ @worksheets_t._Table(&block)
168
+ }
169
+ end
170
+
171
+ end
172
+ end
data/lib/xmlss/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Xmlss
2
- VERSION = "0.3.1"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -1,50 +1,116 @@
1
- require 'xmlss/item_set'
2
- require 'xmlss/xml'
3
-
1
+ require 'xmlss/undies_writer'
4
2
  require 'xmlss/style/base'
5
- require 'xmlss/worksheet'
3
+ require 'xmlss/element/worksheet'
6
4
 
7
5
  module Xmlss
8
6
  class Workbook
9
7
 
10
- include Xmlss::Xml
11
- def xml
12
- { :node => :workbook,
13
- :children => [:styles, :worksheets] }
8
+ def initialize(opts={}, &build)
9
+ # (don't pollute workbook scope that the build may run in)
10
+
11
+ # apply :data options to workbook scope
12
+ data = (opts || {})[:data] || {}
13
+ if (data.keys.map(&:to_s) & self.public_methods.map(&:to_s)).size > 0
14
+ raise ArgumentError, "data conflicts with template public methods."
15
+ end
16
+ metaclass = class << self; self; end
17
+ data.each {|key, value| metaclass.class_eval { define_method(key){value} }}
18
+
19
+ # setup the Undies xml writer with any :output options
20
+ @__xmlss_undies_writer = UndiesWriter.new((opts || {})[:output] || {})
21
+
22
+ # run any instance workbook build given
23
+ instance_eval(&build) if build
24
+ end
25
+
26
+ def to_s
27
+ @__xmlss_undies_writer.workbook
28
+ end
29
+
30
+ def to_file(path)
31
+ FileUtils.mkdir_p(File.dirname(path))
32
+ File.open(path, 'w') { |f| f.write self.to_s }
33
+ File.exists?(path) ? path : false
34
+ end
35
+
36
+ # Workbook elements API
37
+
38
+ def worksheet(*args, &block)
39
+ Element::Worksheet.new(*args).tap do |elem|
40
+ @__xmlss_undies_writer.worksheet(elem, &block)
41
+ end
42
+ end
43
+
44
+ def column(*args, &block)
45
+ Element::Column.new(*args).tap do |elem|
46
+ @__xmlss_undies_writer.column(elem, &block)
47
+ end
48
+ end
49
+
50
+ def row(*args, &block)
51
+ Element::Row.new(*args).tap do |elem|
52
+ @__xmlss_undies_writer.row(elem, &block)
53
+ end
54
+ end
55
+
56
+ def cell(*args, &block)
57
+ Element::Cell.new(*args).tap do |elem|
58
+ @__xmlss_undies_writer.cell(elem, &block)
59
+ end
60
+ end
61
+
62
+ def data(*args, &block)
63
+ Element::Data.new(*args).tap do |elem|
64
+ @__xmlss_undies_writer.data(elem, &block)
65
+ end
14
66
  end
15
67
 
16
- attr_accessor :styles, :worksheets
68
+ # Workbook styles API
17
69
 
18
- def initialize(attrs={})
19
- self.styles = attrs[:styles]
20
- self.worksheets = attrs[:worksheets]
70
+ def style(*args, &block)
71
+ Style::Base.new(*args).tap do |style|
72
+ @__xmlss_undies_writer.style(style, &block)
73
+ end
21
74
  end
22
75
 
23
- def styles=(collection)
24
- @styles = if (set = collection || []).kind_of?(ItemSet)
25
- set
26
- else
27
- ItemSet.new(:styles, set)
76
+ def alignment(*args, &block)
77
+ Style::Alignment.new(*args).tap do |style|
78
+ @__xmlss_undies_writer.alignment(style, &block)
28
79
  end
29
80
  end
30
81
 
31
- def worksheets=(collection)
32
- @worksheets = if (set = collection || []).kind_of?(ItemSet)
33
- set
34
- else
35
- ItemSet.new(nil, set)
82
+ def borders(*args, &block)
83
+ @__xmlss_undies_writer.borders(&block)
84
+ end
85
+
86
+ def border(*args, &block)
87
+ Style::Border.new(*args).tap do |style|
88
+ @__xmlss_undies_writer.border(style, &block)
36
89
  end
37
90
  end
38
91
 
39
- def to_xml(*options)
40
- xml_builder do |builder|
41
- build_node(builder, {
42
- Xmlss::XML_NS => Xmlss::NS_URI,
43
- "#{Xmlss::XML_NS}:#{Xmlss::SHEET_NS}" => Xmlss::NS_URI
44
- })
45
- end.
46
- to_xml({:save_with => xml_save_with(*options)}).
47
- gsub(/#{Xmlss::Data::LB.gsub(/&/, "&amp;")}/, Xmlss::Data::LB)
92
+ def font(*args, &block)
93
+ Style::Font.new(*args).tap do |style|
94
+ @__xmlss_undies_writer.font(style, &block)
95
+ end
96
+ end
97
+
98
+ def interior(*args, &block)
99
+ Style::Interior.new(*args).tap do |style|
100
+ @__xmlss_undies_writer.interior(style, &block)
101
+ end
102
+ end
103
+
104
+ def number_format(*args, &block)
105
+ Style::NumberFormat.new(*args).tap do |style|
106
+ @__xmlss_undies_writer.number_format(style, &block)
107
+ end
108
+ end
109
+
110
+ def protection(*args, &block)
111
+ Style::Protection.new(*args).tap do |style|
112
+ @__xmlss_undies_writer.protection(style, &block)
113
+ end
48
114
  end
49
115
 
50
116
  end
data/lib/xmlss.rb CHANGED
@@ -1,31 +1,4 @@
1
- module Xmlss
2
- # some helpers
3
- class << self
1
+ module Xmlss; end
4
2
 
5
- def classify(underscored_string)
6
- underscored_string.
7
- to_s.downcase.
8
- split("_").
9
- collect{|part| part.capitalize}.
10
- join('')
11
- end
12
-
13
- def xmlify(value)
14
- if value == true
15
- 1
16
- elsif ["",false].include?(value)
17
- # don't include false or empty string values
18
- nil
19
- else
20
- value
21
- end
22
- end
23
-
24
- end
25
- end
26
-
27
- require 'enumeration'
28
- require 'xmlss/xml'
29
- require 'xmlss/style/base'
30
3
  require 'xmlss/workbook'
31
4
 
@@ -1,23 +1,22 @@
1
1
  require "assert"
2
- require 'xmlss/cell'
3
2
 
4
- module Xmlss
3
+ require 'xmlss/element/cell'
4
+
5
+ module Xmlss::Element
5
6
  class CellTest < Assert::Context
6
7
  desc "Xmlss::Cell"
7
- subject { Cell.new }
8
+ before { @c = Cell.new }
9
+ subject { @c }
8
10
 
9
- should have_accessor :index, :data
10
- should have_accessor :formula, :href, :merge_across, :merge_down
11
+ should be_styled
12
+ should have_accessor :index, :formula, :href, :merge_across, :merge_down
11
13
  should have_reader :h_ref
12
14
 
13
- should_have_style(Cell)
14
-
15
15
  should "set it's defaults" do
16
- [:data, :formula, :href].each do |a|
17
- assert_equal nil, subject.send(a)
18
- end
19
- assert_equal nil, subject.merge_across
20
- assert_equal nil, subject.merge_down
16
+ assert_nil subject.formula
17
+ assert_nil subject.href
18
+ assert_nil subject.merge_across
19
+ assert_nil subject.merge_down
21
20
  end
22
21
 
23
22
  should "provide alias for :href" do
@@ -63,27 +62,4 @@ module Xmlss
63
62
 
64
63
  end
65
64
 
66
- class CellDataTest < Assert::Context
67
- desc "when using cell data"
68
- subject do
69
- Cell.new({
70
- :data => Data.new(12, {:type => :number})
71
- })
72
- end
73
-
74
- should "should build a data object" do
75
- assert_kind_of Data, subject.data
76
- assert_equal 12, subject.data.value
77
- end
78
-
79
- end
80
-
81
- class CellDataXmlTest < CellDataTest
82
- desc "for generating XML"
83
-
84
- should have_reader :xml
85
- should_build_node
86
-
87
- end
88
-
89
65
  end
@@ -1,12 +1,14 @@
1
1
  require "assert"
2
- require 'xmlss/column'
3
2
 
4
- module Xmlss
3
+ require 'xmlss/element/column'
4
+
5
+ module Xmlss::Element
5
6
  class ColumnTest < Assert::Context
6
7
  desc "Xmlss::Column"
7
- subject { Column.new }
8
+ before { @c = Column.new }
9
+ subject { @c }
8
10
 
9
- should_have_style(Column)
11
+ should be_styled
10
12
  should have_accessor :width, :auto_fit_width, :hidden
11
13
 
12
14
  should "set it's defaults" do
@@ -36,12 +38,4 @@ module Xmlss
36
38
 
37
39
  end
38
40
 
39
- class ColumnXmlTest < ColumnTest
40
- desc "for generating XML"
41
-
42
- should have_reader :xml
43
- should_build_node
44
-
45
- end
46
-
47
41
  end
@@ -1,12 +1,24 @@
1
1
  require "assert"
2
- require 'xmlss/data'
2
+ require 'enumeration/assert_macros'
3
3
 
4
- module Xmlss
4
+ require 'xmlss/element/data'
5
+
6
+ module Xmlss::Element
5
7
  class DataTest < Assert::Context
8
+ include Enumeration::AssertMacros
9
+
6
10
  desc "Xmlss::Data"
7
11
  subject { Data.new }
8
12
 
9
- should have_accessor :type, :value
13
+ should have_enum :type, {
14
+ :number => "Number",
15
+ :date_time => "DateTime",
16
+ :boolean => "Boolean",
17
+ :string => "String",
18
+ :error => "Error"
19
+ }
20
+
21
+ should have_accessor :value
10
22
  should have_instance_method :xml_value
11
23
 
12
24
  should "set it's defaults" do
@@ -72,11 +84,4 @@ Should
72
84
 
73
85
  end
74
86
 
75
- class XmlDataTest < DataTest
76
- desc "for generating XML"
77
-
78
- should have_reader :xml
79
- should_build_node
80
- end
81
-
82
87
  end
@@ -1,31 +1,20 @@
1
1
  require "assert"
2
- require 'xmlss/cell'
3
- require 'xmlss/row'
4
2
 
5
- module Xmlss
3
+ require 'xmlss/element/row'
4
+
5
+ module Xmlss::Element
6
6
  class RowTest < Assert::Context
7
7
  desc "Xmlss::Row"
8
8
  before { @row = Row.new }
9
9
  subject { @row }
10
10
 
11
- should_have_style(Row)
11
+ should be_styled
12
12
  should have_accessors :height, :auto_fit_height, :hidden
13
- should have_accessor :cells
14
13
 
15
14
  should "set it's defaults" do
16
- assert_equal nil, subject.height
15
+ assert_nil subject.height
17
16
  assert_equal false, subject.auto_fit_height
18
17
  assert_equal false, subject.hidden
19
- assert_equal [], subject.cells
20
- end
21
-
22
- should "allow defining a cells at init" do
23
- row = Row.new({
24
- :cells => [Cell.new]
25
- })
26
-
27
- assert_equal 1, row.cells.size
28
- assert_kind_of Cell, row.cells.first
29
18
  end
30
19
 
31
20
  should "bark when setting non Numeric height" do
@@ -48,23 +37,4 @@ module Xmlss
48
37
  end
49
38
  end
50
39
 
51
- class RowCellsTest < RowTest
52
- desc "when using cells"
53
- before do
54
- r = subject.cells << Cell.new
55
- end
56
-
57
- should "should build a data object" do
58
- assert_equal 1, subject.cells.size
59
- assert_kind_of Cell, subject.cells.first
60
- end
61
- end
62
-
63
- class RowXmlTest < RowTest
64
- desc "for generating XML"
65
-
66
- should have_reader :xml
67
- should_build_node
68
- end
69
-
70
40
  end
@@ -1,19 +1,17 @@
1
1
  require "assert"
2
- require 'xmlss/worksheet'
3
2
 
4
- module Xmlss
3
+ require 'xmlss/element/worksheet'
4
+
5
+ module Xmlss::Element
5
6
  class WorksheetTest < Assert::Context
6
7
  desc "Xmlss::Worksheet"
7
8
  before { @wksht = Worksheet.new('sheet') }
8
9
  subject { @wksht }
9
10
 
10
- should have_accessor :name, :table
11
+ should have_accessor :name
11
12
 
12
13
  should "set it's defaults" do
13
14
  assert_equal 'sheet', subject.name
14
- assert_kind_of Table, subject.table
15
- assert_equal [], subject.table.columns
16
- assert_equal [], subject.table.rows
17
15
  end
18
16
 
19
17
  should "filter name chars" do
@@ -31,17 +29,6 @@ module Xmlss
31
29
  assert_equal "t[e]st test", ws.name
32
30
  end
33
31
 
34
- should "allow defining a table at init" do
35
- wksht = Worksheet.new('table', {
36
- :table => Table.new({
37
- :columns => [Column.new]
38
- })
39
- })
40
-
41
- assert_equal 1, wksht.table.columns.size
42
- assert_kind_of Column, wksht.table.columns.first
43
- end
44
-
45
32
  should "bark when no name is given" do
46
33
  assert_raises ArgumentError do
47
34
  Worksheet.new(nil)
@@ -51,19 +38,6 @@ module Xmlss
51
38
  end
52
39
  end
53
40
 
54
- should "bark when setting a table to something other" do
55
- assert_raises ArgumentError do
56
- subject.table = "not a table"
57
- end
58
- end
59
-
60
- end
61
-
62
- class WorksheetXmlTest < WorksheetTest
63
- desc "for generating XML"
64
-
65
- should have_reader :xml
66
- should_build_node
67
41
  end
68
42
 
69
43
  end
data/test/helper.rb CHANGED
@@ -4,42 +4,25 @@
4
4
  # add root dir to the load path
5
5
  $LOAD_PATH.unshift(File.expand_path("../..", __FILE__))
6
6
 
7
- require 'xmlss'
8
-
9
7
  class Assert::Context
10
8
 
11
9
  class << self
12
10
 
13
- def should_build_node
14
- should have_instance_methods :build_node
15
- should "build it's node" do
16
- assert_nothing_raised do
17
- ::Nokogiri::XML::Builder.new do |builder|
18
- subject.build_node(builder)
19
- end
20
- end
21
- end
22
- end
11
+ def be_styled
12
+ called_from = caller.first
13
+ Assert::Macro.new("have style attributes") do
14
+ should have_accessor :style_id
15
+ should have_reader :style_i_d
23
16
 
24
- def should_build_no_attributes_by_default(klass)
25
- should "have no element attributes" do
26
- xmlthing = klass.new
27
- assert_equal({}, xmlthing.send(:build_attributes))
28
- end
29
- end
30
-
31
- def should_have_style(klass)
32
- should have_accessor :style_id
33
- should have_reader :style_i_d
34
-
35
- should "set the style default" do
36
- assert_equal nil, subject.style_id
37
- end
17
+ should "set the style default" do
18
+ assert_equal nil, subject.class.new.style_id
19
+ end
38
20
 
39
- should "provide aliases for style_id" do
40
- c = klass.new({:style_id => :poo})
41
- assert_equal :poo, c.style_id
42
- assert_equal :poo, c.style_i_d
21
+ should "provide aliases for style_id" do
22
+ c = subject.class.new({:style_id => :poo})
23
+ assert_equal :poo, c.style_id
24
+ assert_equal :poo, c.style_i_d
25
+ end
43
26
  end
44
27
  end
45
28