xmlss 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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