xmlss 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/.gitignore +6 -0
  2. data/Gemfile +4 -0
  3. data/Gemfile.lock +28 -0
  4. data/README.rdoc +154 -0
  5. data/Rakefile +7 -0
  6. data/examples/example_workbook.rb +19 -0
  7. data/examples/layout.rb +81 -0
  8. data/examples/simple.rb +30 -0
  9. data/examples/styles.rb +85 -0
  10. data/examples/text.rb +36 -0
  11. data/lib/xmlss/cell.rb +38 -0
  12. data/lib/xmlss/column.rb +28 -0
  13. data/lib/xmlss/data.rb +67 -0
  14. data/lib/xmlss/enum.rb +56 -0
  15. data/lib/xmlss/item_set.rb +17 -0
  16. data/lib/xmlss/row.rb +34 -0
  17. data/lib/xmlss/style/alignment.rb +47 -0
  18. data/lib/xmlss/style/base.rb +63 -0
  19. data/lib/xmlss/style/border.rb +43 -0
  20. data/lib/xmlss/style/font.rb +41 -0
  21. data/lib/xmlss/style/interior.rb +41 -0
  22. data/lib/xmlss/style/number_format.rb +20 -0
  23. data/lib/xmlss/style/protection.rb +18 -0
  24. data/lib/xmlss/table.rb +22 -0
  25. data/lib/xmlss/version.rb +3 -0
  26. data/lib/xmlss/workbook.rb +33 -0
  27. data/lib/xmlss/worksheet.rb +37 -0
  28. data/lib/xmlss/xml.rb +55 -0
  29. data/lib/xmlss.rb +31 -0
  30. data/test/cell_test.rb +86 -0
  31. data/test/column_test.rb +46 -0
  32. data/test/data_test.rb +75 -0
  33. data/test/enum_test.rb +63 -0
  34. data/test/env.rb +10 -0
  35. data/test/helper.rb +49 -0
  36. data/test/item_set_test.rb +26 -0
  37. data/test/row_test.rb +67 -0
  38. data/test/style/alignment_test.rb +107 -0
  39. data/test/style/base_test.rb +126 -0
  40. data/test/style/border_test.rb +114 -0
  41. data/test/style/font_test.rb +100 -0
  42. data/test/style/interior_test.rb +87 -0
  43. data/test/style/number_format_test.rb +50 -0
  44. data/test/style/protection_test.rb +45 -0
  45. data/test/table_test.rb +53 -0
  46. data/test/thing.rb +5 -0
  47. data/test/workbook_test.rb +59 -0
  48. data/test/worksheet_test.rb +62 -0
  49. data/test/xml_test.rb +61 -0
  50. data/test/xmlss_test.rb +29 -0
  51. data/xmlss.gemspec +23 -0
  52. metadata +184 -0
data/lib/xmlss/row.rb ADDED
@@ -0,0 +1,34 @@
1
+ require 'xmlss/item_set'
2
+ require 'xmlss/cell'
3
+
4
+ module Xmlss
5
+ class Row
6
+
7
+ include Xmlss::Xml
8
+ def xml
9
+ { :node => :row,
10
+ :attributes => [:style_i_d, :height, :auto_fit_height, :hidden],
11
+ :children => [:cells] }
12
+ end
13
+
14
+ attr_accessor :style_id, :height, :auto_fit_height, :hidden
15
+ attr_accessor :cells
16
+ alias_method :style_i_d, :style_id
17
+
18
+ def initialize(attrs={})
19
+ self.style_id = attrs[:style_id]
20
+ self.height = attrs[:height]
21
+ self.auto_fit_height = attrs[:auto_fit_height] || false
22
+ self.hidden = attrs[:hidden] || false
23
+ self.cells = Xmlss::ItemSet.new(nil, attrs[:cells] || [])
24
+ end
25
+
26
+ def height=(value)
27
+ if value && !value.kind_of?(::Numeric)
28
+ raise ArgumentError, "must specify height as a Numeric"
29
+ end
30
+ @height = value && value < 0 ? nil : value
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,47 @@
1
+ module Xmlss::Style
2
+ class Alignment
3
+ include Xmlss::Xml
4
+ def xml
5
+ { :node => :alignment,
6
+ :attributes => [:horizontal, :vertical, :wrap_text, :rotate] }
7
+ end
8
+
9
+ include Xmlss::Enum
10
+ enum :horizontal, {
11
+ :automatic => "Automatic",
12
+ :left => "Left",
13
+ :center => "Center",
14
+ :right => "Right"
15
+ }
16
+ enum :vertical, {
17
+ :automatic => "Automatic",
18
+ :top => "Top",
19
+ :center => "Center",
20
+ :bottom => "Bottom"
21
+ }
22
+
23
+ attr_accessor :wrap_text, :rotate
24
+
25
+ def initialize(attrs={})
26
+ self.wrap_text = attrs[:wrap_text] || false
27
+ self.rotate = attrs[:rotate]
28
+ self.horizontal = attrs[:horizontal]
29
+ self.vertical = attrs[:vertical]
30
+ end
31
+
32
+ def wrap_text?; !!self.wrap_text; end
33
+
34
+ def rotate=(value)
35
+ @rotate = if value.kind_of?(::Numeric)
36
+ if value <= 90 && value >= -90
37
+ value.round
38
+ else
39
+ nil
40
+ end
41
+ else
42
+ nil
43
+ end
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,63 @@
1
+ require 'xmlss/item_set'
2
+ require 'xmlss/style/alignment'
3
+ require 'xmlss/style/border'
4
+ require 'xmlss/style/font'
5
+ require 'xmlss/style/interior'
6
+ require 'xmlss/style/number_format'
7
+ require 'xmlss/style/protection'
8
+
9
+ module Xmlss::Style
10
+ class Base
11
+ include Xmlss::Xml
12
+ def xml
13
+ { :node => :style,
14
+ :attributes => [:i_d],
15
+ :children => [
16
+ :alignment, :borders, :font, :interior, :number_format, :protection
17
+ ]}
18
+ end
19
+
20
+ attr_reader :id, :borders
21
+ alias_method :i_d, :id
22
+ attr_writer :alignment, :font, :interior, :number_format, :protection
23
+
24
+ def initialize(id, &block)
25
+ raise ArgumentError, "please choose an id for the style" if id.nil?
26
+ @id = id.to_s
27
+ self.borders = Xmlss::ItemSet.new(:borders)
28
+ instance_eval(&block) if block
29
+ end
30
+
31
+ def borders=(value)
32
+ if !value.kind_of? Xmlss::ItemSet
33
+ raise ArgumentError, "must set borders to an Xmlss::ItemSet value"
34
+ end
35
+ @borders = value
36
+ end
37
+
38
+ def border(opts = nil)
39
+ valid_attrs?(opts) do |attrs|
40
+ @borders << Border.new(attrs)
41
+ end
42
+ end
43
+
44
+ [:alignment, :font, :interior, :number_format, :protection].each do |meth|
45
+ define_method(meth) do |*args|
46
+ valid_attrs?(args.first) do |attrs|
47
+ instance_variable_set("@#{meth}", klass(meth).new(attrs))
48
+ end
49
+ instance_variable_get("@#{meth}")
50
+ end
51
+ end
52
+
53
+ protected
54
+
55
+ def valid_attrs?(attrs)
56
+ yield attrs if block_given? && attrs && attrs.kind_of?(::Hash)
57
+ end
58
+
59
+ def klass(method_name)
60
+ Xmlss::Style.const_get(Xmlss.classify(method_name))
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,43 @@
1
+ module Xmlss::Style
2
+ class Border
3
+ include Xmlss::Xml
4
+ def xml
5
+ { :node => :border,
6
+ :attributes => [:color, :position, :weight, :line_style] }
7
+ end
8
+
9
+ include Xmlss::Enum
10
+ enum :position, {
11
+ :left => "Left",
12
+ :top => "Top",
13
+ :right => "Right",
14
+ :bottom => "Bottom",
15
+ :diagonal_left => "DiagonalLeft",
16
+ :diagonal_right => "DiagonalRight"
17
+ }
18
+ enum :weight, {
19
+ :hairline => 0,
20
+ :thin => 1,
21
+ :medium => 2,
22
+ :thick => 3
23
+ }
24
+ enum :line_style, {
25
+ :none => "None",
26
+ :continuous => "Continuous",
27
+ :dash => "Dash",
28
+ :dot => "Dot",
29
+ :dash_dot => "DashDot",
30
+ :dash_dot_dot => "DashDotDot"
31
+ }
32
+
33
+ attr_accessor :color
34
+
35
+ def initialize(attrs={})
36
+ self.color = attrs[:color]
37
+ self.position = attrs[:position]
38
+ self.weight = attrs[:weight] || :thin
39
+ self.line_style = attrs[:line_style] || :continuous
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,41 @@
1
+ module Xmlss::Style
2
+ class Font
3
+ include Xmlss::Xml
4
+ def xml
5
+ { :node => :font,
6
+ :attributes => [
7
+ :bold, :color, :italic, :size,
8
+ :strike_through, :underline, :alignment
9
+ ] }
10
+ end
11
+
12
+ include Xmlss::Enum
13
+ enum :underline, {
14
+ :none => 0,
15
+ :single => 1,
16
+ :double => 2
17
+ }
18
+ enum :alignment, {
19
+ :none => 0,
20
+ :subscript => 1,
21
+ :superscript => 2
22
+ }
23
+
24
+ attr_accessor :bold, :color, :italic, :size, :strike_through
25
+
26
+ def initialize(attrs={})
27
+ self.bold = attrs[:bold] || false
28
+ self.color = attrs[:color]
29
+ self.italic = attrs[:italic] || false
30
+ self.size = attrs[:size]
31
+ self.strike_through = attrs[:strike_through] || false
32
+ self.underline = attrs[:underline]
33
+ self.alignment = attrs[:alignment]
34
+ end
35
+
36
+ def bold?; !!self.bold; end
37
+ def italic?; !!self.italic; end
38
+ def strike_through?; !!self.strike_through; end
39
+
40
+ end
41
+ end
@@ -0,0 +1,41 @@
1
+ module Xmlss::Style
2
+ class Interior
3
+ include Xmlss::Xml
4
+ def xml
5
+ { :node => :interior,
6
+ :attributes => [:color, :pattern, :pattern_color] }
7
+ end
8
+
9
+ include Xmlss::Enum
10
+ enum :pattern, {
11
+ :none => "None",
12
+ :solid => "Solid",
13
+ :gray75 => "Gray75",
14
+ :gray50 => "Gray50",
15
+ :gray25 => "Gray25",
16
+ :gray125 => "Gray125",
17
+ :gray0625 => "Gray0625",
18
+ :horz_stripe => "HorzStripe",
19
+ :vert_stripe => "VertStripe",
20
+ :reverse_diag_stripe => "ReverseDiagStripe",
21
+ :diag_stripe => "DiagStripe",
22
+ :diag_cross => "DiagCross",
23
+ :thick_diag_cross => "ThickDiagCross",
24
+ :thin_horz_stripe => "ThinHorzStripe",
25
+ :thin_vert_stripe => "ThinVertStripe",
26
+ :thin_reverse_diag_stripe => "ThinReverseDiagStripe",
27
+ :thin_diag_stripe => "ThineDiagStripe",
28
+ :thin_horz_cross => "ThinHorzCross",
29
+ :thin_diag_cross => "ThinDiagCross"
30
+ }
31
+
32
+ attr_accessor :color, :pattern_color
33
+
34
+ def initialize(attrs={})
35
+ self.color = attrs[:color]
36
+ self.pattern = attrs[:pattern]
37
+ self.pattern_color = attrs[:pattern_color]
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,20 @@
1
+ module Xmlss::Style
2
+ class NumberFormat
3
+ include Xmlss::Xml
4
+ def xml
5
+ { :node => :number_format,
6
+ :attributes => [:format] }
7
+ end
8
+
9
+ attr_accessor :format
10
+
11
+ def initialize(attrs={})
12
+ self.format = attrs[:format]
13
+ end
14
+
15
+ def format=(value)
16
+ @format = (value && value.respond_to?(:to_s) ? value.to_s : nil)
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,18 @@
1
+ module Xmlss::Style
2
+ class Protection
3
+ include Xmlss::Xml
4
+ def xml
5
+ { :node => :protection,
6
+ :attributes => [:protect] }
7
+ end
8
+
9
+ attr_accessor :protect
10
+
11
+ def initialize(attrs={})
12
+ self.protect = attrs[:protect] || false
13
+ end
14
+
15
+ def protected?; !!self.protect; end
16
+
17
+ end
18
+ end
@@ -0,0 +1,22 @@
1
+ require 'xmlss/item_set'
2
+ require 'xmlss/column'
3
+ require 'xmlss/row'
4
+
5
+ module Xmlss
6
+ class Table
7
+
8
+ include Xmlss::Xml
9
+ def xml
10
+ { :node => :table,
11
+ :children => [:columns, :rows] }
12
+ end
13
+
14
+ attr_accessor :columns, :rows
15
+
16
+ def initialize(attrs={})
17
+ self.columns = Xmlss::ItemSet.new(nil, attrs[:columns] || [])
18
+ self.rows = Xmlss::ItemSet.new(nil, attrs[:rows] || [])
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,3 @@
1
+ module Xmlss
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,33 @@
1
+ require 'xmlss/item_set'
2
+ require 'xmlss/xml'
3
+
4
+ require 'xmlss/style/base'
5
+ require 'xmlss/worksheet'
6
+
7
+ module Xmlss
8
+ class Workbook
9
+
10
+ include Xmlss::Xml
11
+ def xml
12
+ { :node => :workbook,
13
+ :children => [:styles, :worksheets] }
14
+ end
15
+
16
+ attr_accessor :styles, :worksheets
17
+
18
+ def initialize(attrs={})
19
+ self.styles = Xmlss::ItemSet.new(:styles, attrs[:styles] || [])
20
+ self.worksheets = Xmlss::ItemSet.new(nil, attrs[:worksheets] || [])
21
+ end
22
+
23
+ def to_xml
24
+ xml_builder do |builder|
25
+ build_node(builder, {
26
+ Xmlss::XML_NS => Xmlss::NS_URI,
27
+ "#{Xmlss::XML_NS}:#{Xmlss::SHEET_NS}" => Xmlss::NS_URI
28
+ })
29
+ end.to_xml.gsub(/#{Xmlss::Data::LB.gsub(/&/, "&amp;")}/, Xmlss::Data::LB)
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,37 @@
1
+ require 'xmlss/table'
2
+
3
+ module Xmlss
4
+ class Worksheet
5
+
6
+ include Xmlss::Xml
7
+ def xml
8
+ { :node => :worksheet,
9
+ :attributes => [:name],
10
+ :children => [:table] }
11
+ end
12
+
13
+ attr_accessor :name, :table
14
+
15
+ def initialize(name, attrs={})
16
+ if name.nil? || name.empty?
17
+ raise ArgumentError, "'#{name.inspect}' is not a good name for a worksheet"
18
+ end
19
+ self.name = sanitized_name(name)
20
+ self.table = attrs[:table] || Table.new
21
+ end
22
+
23
+ def table=(value)
24
+ if value.nil? || !value.kind_of?(Table)
25
+ raise ArgumentError, "you must set table to an actual Table object"
26
+ end
27
+ @table = value
28
+ end
29
+
30
+ private
31
+
32
+ def sanitized_name(value)
33
+ # worksheet name cannot contain: /, \, ?, *, [, ]
34
+ value.to_s.gsub(/[\/|\\|\?|\*|\[|\]]/, '')
35
+ end
36
+ end
37
+ end
data/lib/xmlss/xml.rb ADDED
@@ -0,0 +1,55 @@
1
+ require 'nokogiri'
2
+
3
+ # common methods used for generating XML
4
+ module Xmlss
5
+ XML_NS = "xmlns"
6
+ SHEET_NS = "ss"
7
+ NS_URI = "urn:schemas-microsoft-com:office:spreadsheet"
8
+
9
+ module Xml
10
+
11
+ def xml_builder
12
+ ::Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |builder|
13
+ yield(builder) if block_given?
14
+ end
15
+ end
16
+
17
+ def build_node(builder, attrs={})
18
+ unless xml && xml.kind_of?(::Hash)
19
+ raise ArgumentError, "no xml config provided"
20
+ end
21
+ if xml[:node] && !xml[:node].to_s.empty?
22
+ if xml[:value] && (v = self.send(xml[:value]).to_s)
23
+ builder.send(Xmlss.classify(xml[:node]), v, build_attributes(attrs))
24
+ else
25
+ builder.send(Xmlss.classify(xml[:node]), build_attributes(attrs)) do
26
+ build_children(builder)
27
+ end
28
+ end
29
+ else
30
+ build_children(builder)
31
+ end
32
+ end
33
+
34
+ def build_attributes(attrs={})
35
+ (xml[:attributes] || []).inject({}) do |xattrs, a|
36
+ xattrs.merge(if !(xv = Xmlss.xmlify(self.send(a))).nil?
37
+ {"#{Xmlss::SHEET_NS}:#{Xmlss.classify(a)}" => xv}
38
+ else
39
+ {}
40
+ end)
41
+ end.merge(attrs)
42
+ end
43
+ private :build_attributes
44
+
45
+ def build_children(builder)
46
+ (xml[:children] || []).each do |c|
47
+ if (child = c.kind_of?(::Symbol) ? self.send(c) : c)
48
+ child.build_node(builder) if child.respond_to?(:build_node)
49
+ end
50
+ end
51
+ end
52
+ private :build_children
53
+
54
+ end
55
+ end
data/lib/xmlss.rb ADDED
@@ -0,0 +1,31 @@
1
+ module Xmlss
2
+ # some helpers
3
+ class << self
4
+
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 'xmlss/xml'
28
+ require 'xmlss/enum'
29
+ require 'xmlss/style/base'
30
+ require 'xmlss/workbook'
31
+
data/test/cell_test.rb ADDED
@@ -0,0 +1,86 @@
1
+ require "test/helper"
2
+ require 'xmlss/cell'
3
+
4
+ module Xmlss
5
+ class CellTest < Test::Unit::TestCase
6
+
7
+ context "Xmlss::Cell" do
8
+ subject { Cell.new }
9
+
10
+ should_have_accessor :index, :data
11
+ should_have_accessor :formula, :href, :merge_across, :merge_down
12
+ should_have_reader :h_ref
13
+
14
+ should_have_style(Cell)
15
+
16
+ should "set it's defaults" do
17
+ [:data, :formula, :href].each do |a|
18
+ assert_equal nil, subject.send(a)
19
+ end
20
+ assert_equal nil, subject.merge_across
21
+ assert_equal nil, subject.merge_down
22
+ end
23
+
24
+ should "provide alias for :href" do
25
+ c = Cell.new({:href => "http://www.google.com"})
26
+ assert_equal "http://www.google.com", c.href
27
+ assert_equal "http://www.google.com", c.h_ref
28
+ end
29
+
30
+ should "bark when setting non Fixnum indices" do
31
+ assert_raises ArgumentError do
32
+ Cell.new({:index => "do it"})
33
+ end
34
+ assert_raises ArgumentError do
35
+ Cell.new({:index => 2.5})
36
+ end
37
+ assert_nothing_raised do
38
+ Cell.new({:index => 2})
39
+ end
40
+ end
41
+
42
+ should "bark when setting non Fixnum merge attrs" do
43
+ assert_raises ArgumentError do
44
+ Cell.new({:merge_across => "do it"})
45
+ end
46
+ assert_raises ArgumentError do
47
+ Cell.new({:merge_down => 2.5})
48
+ end
49
+ assert_nothing_raised do
50
+ Cell.new({:merge_across => 2})
51
+ end
52
+ assert_nothing_raised do
53
+ Cell.new({:merge_down => 3})
54
+ end
55
+ end
56
+
57
+ should "nil out merge/index values that are <= 0" do
58
+ [:index, :merge_across, :merge_down].each do |a|
59
+ assert_equal nil, Cell.new({a => -1}).send(a)
60
+ assert_equal nil, Cell.new({a => 0}).send(a)
61
+ assert_equal 1, Cell.new({a => 1}).send(a)
62
+ end
63
+ end
64
+
65
+ context "when using cell data" do
66
+ subject do
67
+ Cell.new({
68
+ :data => Data.new(12, {:type => :number})
69
+ })
70
+ end
71
+
72
+ should "should build a data object" do
73
+ assert_kind_of Data, subject.data
74
+ assert_equal 12, subject.data.value
75
+ end
76
+ end
77
+
78
+ context "for generating XML" do
79
+ should_have_reader :xml
80
+ should_build_node
81
+ end
82
+
83
+ end
84
+
85
+ end
86
+ end
@@ -0,0 +1,46 @@
1
+ require "test/helper"
2
+ require 'xmlss/column'
3
+
4
+ module Xmlss
5
+ class ColumnTest < Test::Unit::TestCase
6
+
7
+ context "Xmlss::Column" do
8
+ subject { Column.new }
9
+
10
+ should_have_style(Column)
11
+ should_have_accessor :width, :auto_fit_width, :hidden
12
+
13
+ should "set it's defaults" do
14
+ assert_equal nil, subject.width
15
+ assert_equal false, subject.auto_fit_width
16
+ assert_equal false, subject.hidden
17
+ end
18
+
19
+ should "bark when setting non Numeric width" do
20
+ assert_raises ArgumentError do
21
+ Column.new({:width => "do it"})
22
+ end
23
+ assert_nothing_raised do
24
+ Column.new({:width => 2})
25
+ end
26
+ assert_nothing_raised do
27
+ Column.new({:width => 3.5})
28
+ end
29
+ end
30
+
31
+ should "nil out height values that are < 0" do
32
+ assert_equal nil, Column.new({:width => -1.2}).width
33
+ assert_equal nil, Column.new({:width => -1}).width
34
+ assert_equal 0, Column.new({:width => 0}).width
35
+ assert_equal 1.2, Column.new({:width => 1.2}).width
36
+ end
37
+
38
+ context "for generating XML" do
39
+ should_have_reader :xml
40
+ should_build_node
41
+ end
42
+
43
+ end
44
+
45
+ end
46
+ end