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.
- data/Gemfile.lock +3 -3
- data/README.rdoc +4 -4
- data/bench/profiler_runner.rb +1 -1
- data/examples/example_workbook.rb +1 -1
- data/examples/simple.rb +18 -1
- data/examples/simple.xml +22 -0
- data/lib/xmlss/element/cell.rb +52 -1
- data/lib/xmlss/element/column.rb +2 -0
- data/lib/xmlss/element/row.rb +2 -0
- data/lib/xmlss/element/worksheet.rb +14 -6
- data/lib/xmlss/element_stack.rb +69 -0
- data/lib/xmlss/style/alignment.rb +2 -0
- data/lib/xmlss/style/base.rb +2 -0
- data/lib/xmlss/style/border.rb +10 -0
- data/lib/xmlss/style/font.rb +2 -0
- data/lib/xmlss/style/interior.rb +2 -0
- data/lib/xmlss/style/number_format.rb +2 -0
- data/lib/xmlss/style/protection.rb +2 -0
- data/lib/xmlss/version.rb +1 -1
- data/lib/xmlss/workbook.rb +75 -58
- data/lib/xmlss/writer.rb +244 -0
- data/test/element/cell_test.rb +60 -1
- data/test/element/column_test.rb +5 -0
- data/test/element/row_test.rb +5 -0
- data/test/element/worksheet_test.rb +9 -4
- data/test/element_stack_test.rb +117 -0
- data/test/style/alignment_test.rb +5 -0
- data/test/style/base_test.rb +5 -0
- data/test/style/border_test.rb +26 -2
- data/test/style/font_test.rb +5 -0
- data/test/style/interior_test.rb +5 -0
- data/test/style/number_format_test.rb +5 -0
- data/test/style/protection_test.rb +6 -1
- data/test/workbook_test.rb +29 -13
- data/test/writer_test.rb +413 -0
- data/xmlss.gemspec +1 -1
- metadata +30 -25
- data/lib/xmlss/element/data.rb +0 -58
- data/lib/xmlss/undies_writer.rb +0 -175
- data/test/element/data_test.rb +0 -67
- 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
|
+
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.
|
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::
|
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(
|
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(:
|
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
|
|
data/bench/profiler_runner.rb
CHANGED
@@ -26,7 +26,7 @@ class XmlssProfilerRunner
|
|
26
26
|
end
|
27
27
|
|
28
28
|
@result = RubyProf.profile do
|
29
|
-
Xmlss::Workbook.new(:
|
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(:
|
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
|
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>
|
data/lib/xmlss/element/cell.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/xmlss/element/column.rb
CHANGED
data/lib/xmlss/element/row.rb
CHANGED
@@ -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(
|
13
|
-
self.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.
|
18
|
-
raise ArgumentError, "
|
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
|
data/lib/xmlss/style/base.rb
CHANGED
data/lib/xmlss/style/border.rb
CHANGED
@@ -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
|
data/lib/xmlss/style/font.rb
CHANGED
data/lib/xmlss/style/interior.rb
CHANGED
data/lib/xmlss/version.rb
CHANGED
data/lib/xmlss/workbook.rb
CHANGED
@@ -1,16 +1,27 @@
|
|
1
|
-
require 'xmlss/
|
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
|
-
|
9
|
-
|
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
|
-
@
|
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
|
-
|
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
|
50
|
+
# Workbook styles API
|
38
51
|
|
39
|
-
def
|
40
|
-
|
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
|
46
|
-
|
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
|
52
|
-
|
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
|
58
|
-
|
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
|
64
|
-
|
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
|
-
|
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
|
78
|
-
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
|
84
|
-
|
80
|
+
def protection(*args, &block)
|
81
|
+
self.class.styles_stack(self).using(Style::Protection.new(*args), &block)
|
85
82
|
end
|
86
83
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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
|
94
|
-
|
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
|
100
|
-
|
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
|
106
|
-
|
107
|
-
|
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
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
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
|