osheet 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. data/.bundle/config +2 -0
  2. data/.gitignore +6 -0
  3. data/Gemfile +4 -0
  4. data/Gemfile.lock +33 -0
  5. data/README.rdoc +103 -61
  6. data/Rakefile +5 -56
  7. data/examples/basic.rb +73 -0
  8. data/examples/formats.rb +366 -0
  9. data/examples/trivial.rb +21 -0
  10. data/lib/osheet/associations.rb +62 -0
  11. data/lib/osheet/cell.rb +44 -10
  12. data/lib/osheet/column.rb +34 -7
  13. data/lib/osheet/format/accounting.rb +21 -0
  14. data/lib/osheet/format/currency.rb +23 -0
  15. data/lib/osheet/format/custom.rb +13 -0
  16. data/lib/osheet/format/datetime.rb +13 -0
  17. data/lib/osheet/format/fraction.rb +33 -0
  18. data/lib/osheet/format/general.rb +9 -0
  19. data/lib/osheet/format/number.rb +13 -0
  20. data/lib/osheet/format/numeric.rb +113 -0
  21. data/lib/osheet/format/percentage.rb +25 -0
  22. data/lib/osheet/format/scientific.rb +25 -0
  23. data/lib/osheet/format/special.rb +28 -0
  24. data/lib/osheet/format/text.rb +11 -0
  25. data/lib/osheet/format.rb +30 -0
  26. data/lib/osheet/row.rb +32 -7
  27. data/lib/osheet/style.rb +65 -0
  28. data/lib/osheet/style_set.rb +39 -0
  29. data/lib/osheet/styled_element.rb +18 -0
  30. data/lib/osheet/template.rb +32 -0
  31. data/lib/osheet/template_set.rb +57 -0
  32. data/lib/osheet/version.rb +1 -11
  33. data/lib/osheet/workbook.rb +32 -16
  34. data/lib/osheet/workbook_element.rb +21 -0
  35. data/lib/osheet/worksheet.rb +18 -20
  36. data/lib/osheet/worksheet_element.rb +15 -0
  37. data/lib/osheet/xmlss_writer/base.rb +42 -0
  38. data/lib/osheet/xmlss_writer/elements.rb +68 -0
  39. data/lib/osheet/xmlss_writer/styles.rb +176 -0
  40. data/lib/osheet/xmlss_writer.rb +1 -0
  41. data/lib/osheet.rb +7 -27
  42. data/osheet.gemspec +26 -0
  43. data/test/cell_test.rb +83 -0
  44. data/test/column_test.rb +81 -0
  45. data/test/env.rb +9 -0
  46. data/test/format/accounting_test.rb +158 -0
  47. data/test/format/currency_test.rb +158 -0
  48. data/test/format/custom_test.rb +22 -0
  49. data/test/format/datetime_test.rb +22 -0
  50. data/test/format/fraction_test.rb +84 -0
  51. data/test/format/general_test.rb +21 -0
  52. data/test/format/number_test.rb +93 -0
  53. data/test/format/percentage_test.rb +116 -0
  54. data/test/format/scientific_test.rb +116 -0
  55. data/test/format/special_test.rb +51 -0
  56. data/test/format/text_test.rb +18 -0
  57. data/test/format_test.rb +35 -0
  58. data/test/helper.rb +101 -0
  59. data/test/osheet_test.rb +14 -0
  60. data/test/row_test.rb +78 -0
  61. data/test/style_set_test.rb +50 -0
  62. data/test/style_test.rb +92 -0
  63. data/test/template_set_test.rb +76 -0
  64. data/test/template_test.rb +63 -0
  65. data/test/workbook_test.rb +142 -0
  66. data/test/worksheet_test.rb +77 -0
  67. data/test/xmlss_writer/base_test.rb +92 -0
  68. data/test/xmlss_writer/elements_test.rb +168 -0
  69. data/test/xmlss_writer/styles_test.rb +253 -0
  70. metadata +141 -30
  71. data/lib/osheet/base.rb +0 -95
@@ -0,0 +1,113 @@
1
+ require 'enumeration'
2
+
3
+ module Osheet::Format
4
+
5
+ class Numeric
6
+ include Enumeration
7
+
8
+ attr_accessor :decimal_places, :comma_separator
9
+ enum :symbol, [:none, :dollar, :euro]
10
+ enum :negative_numbers, [:black, :black_parenth, :red, :red_parenth]
11
+
12
+ def initialize(opts={})
13
+ self.symbol = opts[:symbol] || :none
14
+ self.decimal_places = opts[:decimal_places] || 0
15
+ self.comma_separator = opts.has_key?(:comma_separator) ? opts[:comma_separator] : false
16
+ self.negative_numbers = opts[:negative_numbers] || :black
17
+ end
18
+
19
+ def decimal_places=(value)
20
+ if !value.kind_of?(::Fixnum) || value < 0 || value > 30
21
+ raise ArgumentError, ":decimal_places must be a Fixnum between 0 and 30."
22
+ end
23
+ @decimal_places = value
24
+ end
25
+
26
+ def comma_separator=(value)
27
+ @comma_separator = !!value
28
+ end
29
+
30
+ def style
31
+ negative_numbers_style
32
+ end
33
+
34
+ def key
35
+ "#{key_prefix}_#{symbol_key}_#{decimal_places_key}_#{comma_separator_key}_#{negative_numbers_key}"
36
+ end
37
+
38
+ protected
39
+
40
+ # used by 'key' in Numeric base class, override as necessary
41
+ def key_prefix
42
+ "number"
43
+ end
44
+
45
+ # used by 'symbol_style' in Numeric base class, override as necessary
46
+ def symbol_suffix
47
+ ""
48
+ end
49
+
50
+ # used by 'decimal_places_style' in Numeric base class, override as necessary
51
+ def decimal_places_suffix
52
+ ""
53
+ end
54
+
55
+ private
56
+
57
+
58
+ def numeric_style
59
+ symbol_style +
60
+ comma_separator_style +
61
+ decimal_places_style
62
+ end
63
+
64
+
65
+ def negative_numbers_style
66
+ case @negative_numbers
67
+ when :black
68
+ numeric_style
69
+ when :red
70
+ "#{numeric_style};[Red]#{numeric_style}"
71
+ when :black_parenth
72
+ "#{numeric_style}_);\(#{numeric_style}\)"
73
+ when :red_parenth
74
+ "#{numeric_style}_);[Red]\(#{numeric_style}\)"
75
+ end
76
+ end
77
+ def negative_numbers_key
78
+ @negative_numbers.to_s.gsub('_', '')
79
+ end
80
+
81
+
82
+ def decimal_places_style
83
+ "0#{'.'+'0'*@decimal_places if @decimal_places > 0}#{decimal_places_suffix}"
84
+ end
85
+ def decimal_places_key
86
+ @decimal_places.to_s
87
+ end
88
+
89
+
90
+ def comma_separator_style
91
+ @comma_separator == true ? '#,##' : ''
92
+ end
93
+ def comma_separator_key
94
+ @comma_separator == true ? 'comma' : 'nocomma'
95
+ end
96
+
97
+
98
+ def symbol_style
99
+ case @symbol
100
+ when :dollar
101
+ "\"$\"#{symbol_suffix}"
102
+ when :euro
103
+ "[$€-2]\ #{symbol_suffix}"
104
+ when :none
105
+ ''
106
+ end
107
+ end
108
+ def symbol_key
109
+ @symbol.to_s.gsub('_', '')
110
+ end
111
+
112
+ end
113
+ end
@@ -0,0 +1,25 @@
1
+ require 'enumeration'
2
+ require 'osheet/format/numeric'
3
+
4
+ module Osheet::Format
5
+
6
+ class Percentage < Osheet::Format::Numeric
7
+ def initialize(opts={})
8
+ super({
9
+ :decimal_places => 2
10
+ }.merge(opts))
11
+ end
12
+
13
+ protected
14
+
15
+ # used by 'key' in Numeric base class
16
+ def key_prefix
17
+ "percentage"
18
+ end
19
+
20
+ # used by 'decimal_places_style' in Numeric base class
21
+ def decimal_places_suffix
22
+ "%"
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ require 'enumeration'
2
+ require 'osheet/format/numeric'
3
+
4
+ module Osheet::Format
5
+
6
+ class Scientific < Osheet::Format::Numeric
7
+ def initialize(opts={})
8
+ super({
9
+ :decimal_places => 2
10
+ }.merge(opts))
11
+ end
12
+
13
+ protected
14
+
15
+ # used by 'key' in Numeric base class
16
+ def key_prefix
17
+ "scientific"
18
+ end
19
+
20
+ # used by 'decimal_places_style' in Numeric base class
21
+ def decimal_places_suffix
22
+ "E+00"
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,28 @@
1
+ require 'enumeration'
2
+
3
+ module Osheet::Format
4
+
5
+ class Special
6
+ include Enumeration
7
+
8
+ enum(:type, {
9
+ :zip_code => '00000',
10
+ :zip_code_plus_4 => '00000-0000',
11
+ :phone_number => '[<=9999999]###-####;(###) ###-####',
12
+ :social_security_number => '000-00-0000'
13
+ })
14
+
15
+ def initialize(opts={})
16
+ self.type = opts[:type]
17
+ end
18
+
19
+ def style
20
+ type
21
+ end
22
+
23
+ def key
24
+ "special_#{type_key.to_s.gsub('_', '')}"
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,11 @@
1
+ module Osheet::Format
2
+
3
+ class Text
4
+
5
+ def initialize(opts={}); end
6
+
7
+ def style; '@'; end
8
+ def key; 'text'; end
9
+
10
+ end
11
+ end
@@ -0,0 +1,30 @@
1
+ require 'osheet/format/general'
2
+ require 'osheet/format/number'
3
+ require 'osheet/format/currency'
4
+ require 'osheet/format/accounting'
5
+ require 'osheet/format/datetime'
6
+ require 'osheet/format/percentage'
7
+ require 'osheet/format/fraction'
8
+ require 'osheet/format/scientific'
9
+ require 'osheet/format/text'
10
+ require 'osheet/format/special'
11
+ require 'osheet/format/custom'
12
+
13
+ module Osheet::Format
14
+
15
+ VALUES = [
16
+ :general,:number, :currency, :accounting,
17
+ :datetime, :percentage, :fraction, :scientific,
18
+ :text, :special, :custom
19
+ ]
20
+
21
+ class << self
22
+ def new(type, opts={})
23
+ unless VALUES.include?(type)
24
+ raise ArgumentError, "'#{type.inspect}' is not a valid format type"
25
+ end
26
+ self.const_get(type.to_s.capitalize).new(opts)
27
+ end
28
+ end
29
+
30
+ end
data/lib/osheet/row.rb CHANGED
@@ -1,14 +1,39 @@
1
- require 'osheet/base'
2
1
  require 'osheet/cell'
3
2
 
4
3
  module Osheet
5
- class Row < Osheet::Base
6
-
7
- has :cells => "Cell"
4
+ class Row
5
+ include Associations
6
+ include WorkbookElement
7
+ include WorksheetElement
8
+ include StyledElement
8
9
 
9
- def initialize(args={})
10
- super(args)
10
+ hm :cells
11
+
12
+ def initialize(workbook=nil, worksheet=nil, *args, &block)
13
+ @workbook = workbook
14
+ @worksheet = worksheet
15
+ @height = nil
16
+ @autofit = false
17
+ @hidden = false
18
+ instance_exec(*args, &block) if block_given?
19
+ end
20
+
21
+ def height(value=nil)
22
+ !value.nil? ? @height = value : @height
11
23
  end
12
-
24
+ def autofit(value); @autofit = !!value; end
25
+ def autofit?; @autofit; end
26
+ def hidden(value); @hidden = !!value; end
27
+ def hidden?; @hidden; end
28
+
29
+ def attributes
30
+ {
31
+ :style_class => @style_class,
32
+ :height => @height,
33
+ :autofit => @autofit,
34
+ :hidden => @hidden
35
+ }
36
+ end
37
+
13
38
  end
14
39
  end
@@ -0,0 +1,65 @@
1
+ module Osheet
2
+ class Style
3
+
4
+ # this class is essentially a set of collectors for style settings
5
+ # each setting collects any arguments passed to it and
6
+ # it is up to the drivers to determine how to use the settings
7
+
8
+ BORDER_POSITIONS = [:top, :right, :bottom, :left]
9
+ BORDERS = BORDER_POSITIONS.collect{|p| "border_#{p}".to_sym}
10
+ SETTINGS = [:align, :font, :bg] + BORDERS
11
+
12
+ attr_reader :selectors
13
+
14
+ def initialize(*selectors, &block)
15
+ @selectors = verify(selectors)
16
+ SETTINGS.each do |setting|
17
+ instance_variable_set("@#{setting}", [])
18
+ end
19
+ instance_eval(&block) if block_given?
20
+ end
21
+
22
+ SETTINGS.each do |setting|
23
+ define_method(setting) do |*args|
24
+ instance_variable_set("@#{setting}", instance_variable_get("@#{setting}") + args)
25
+ end
26
+ end
27
+
28
+ def border(*args)
29
+ BORDERS.each do |border|
30
+ send(border, *args)
31
+ end
32
+ end
33
+
34
+ def attributes
35
+ SETTINGS.inject({}) do |attrs, s|
36
+ attrs[s] = instance_variable_get("@#{s}")
37
+ attrs
38
+ end
39
+ end
40
+
41
+ def match?(style_class)
42
+ selectors.inject(false) do |match, s|
43
+ match ||= s.split('.').inject(true) do |result, part|
44
+ result && (part.empty? || style_class.include?(part))
45
+ end
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def verify(selectors)
52
+ selectors.each do |selector|
53
+ if !selector.kind_of?(::String) || invalid?(selector)
54
+ raise ArgumentError, "invalid selector: '#{selector}', selectors must be strings that begin with '.' and con't have spaces or '>'."
55
+ end
56
+ end
57
+ end
58
+
59
+ def invalid?(selector)
60
+ selector =~ /\s+/ ||
61
+ selector =~ /^[^.]/ ||
62
+ selector =~ />+/
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,39 @@
1
+ require 'osheet/style'
2
+
3
+ module Osheet
4
+ class StyleSet < ::Array
5
+
6
+ # this class is an Array has some helper methods. I want to
7
+ # push styles into the set using the '<<' operator, only allow
8
+ # Osheet::Style objs to be pushed, and then be able to reference
9
+ # a particular set of styles using a style class.
10
+
11
+ def initialize
12
+ super
13
+ end
14
+
15
+ def <<(value)
16
+ super if verify(value)
17
+ end
18
+
19
+ # return the style set for the style class
20
+ def for(style_class)
21
+ self.select{|s| s.match?(style_class)}
22
+ end
23
+
24
+ private
25
+
26
+ def styles(class_str)
27
+ end
28
+
29
+ # verify the style, otherwise ArgumentError it up
30
+ def verify(style)
31
+ if style.kind_of?(Style)
32
+ true
33
+ else
34
+ raise ArgumentError, 'you can only push Osheet::Style objs to the style set'
35
+ end
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,18 @@
1
+ module Osheet::StyledElement
2
+ def style_class(value); @style_class = verify_style_class(value); end
3
+
4
+ private
5
+
6
+ def verify_style_class(style_class)
7
+ if !style_class.kind_of?(::String) || invalid_style_class?(style_class)
8
+ raise ArgumentError, "invalid style_class: '#{style_class}', cannot contain '.' or '>'"
9
+ else
10
+ style_class
11
+ end
12
+ end
13
+
14
+ def invalid_style_class?(style_class)
15
+ style_class =~ /\.+/ ||
16
+ style_class =~ />+/
17
+ end
18
+ end
@@ -0,0 +1,32 @@
1
+ module Osheet
2
+ class Template < ::Proc
3
+
4
+ # this class is essentially a way to define a named initializer
5
+ # block and associate that with an osheet element
6
+ # if an element is initialized from a template, the template
7
+ # block will be instance_eval'd for the element being initialized
8
+
9
+ ELEMENTS = ['worksheet', 'column', 'row', 'cell']
10
+
11
+ attr_accessor :element, :name
12
+
13
+ def initialize(element, name)
14
+ verify(element, name)
15
+ @element = element.to_s
16
+ @name = name.to_s
17
+ super()
18
+ end
19
+
20
+ private
21
+
22
+ def verify(element, name)
23
+ unless element.respond_to?(:to_s) && ELEMENTS.include?(element.to_s)
24
+ raise ArgumentError, "you can only define a template for #{ELEMENTS.join(', ')} elements."
25
+ end
26
+ unless name.kind_of?(::String) || name.kind_of?(::Symbol)
27
+ raise ArgumentError, "please use a string or symbol for the template name."
28
+ end
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,57 @@
1
+ require 'osheet/template'
2
+
3
+ module Osheet
4
+ class TemplateSet < ::Hash
5
+
6
+ # this class is a Hash that behaves kinda like a set. I want to
7
+ # push templates into the set using the '<<' operator, only allow
8
+ # Osheet::Template objs to be pushed, and then be able to reference
9
+ # a particular set of templates using a key
10
+
11
+ def initialize
12
+ super
13
+ end
14
+
15
+ def <<(template)
16
+ if (key = verify(template))
17
+ push(key, template)
18
+ end
19
+ end
20
+
21
+ # return the template set for the named element
22
+ def get(element, name)
23
+ lookup(key(element.to_s, name.to_s))
24
+ end
25
+
26
+ private
27
+
28
+ def lookup(key)
29
+ self[key.first][key.last] if self[key.first]
30
+ end
31
+
32
+ # push the template onto the key
33
+ def push(key, template)
34
+ self[key.first][key.last] = template
35
+ end
36
+
37
+ # verify the template, init the key set, and return the key string
38
+ # otherwise ArgumentError it up
39
+ def verify(template)
40
+ unless template.kind_of?(Template)
41
+ raise ArgumentError, 'you can only push Osheet::Template objs to the template set'
42
+ end
43
+ key = template_key(template)
44
+ self[key.first] ||= {}
45
+ self[key.first][key.last] ||= nil
46
+ key
47
+ end
48
+
49
+ def template_key(template)
50
+ key(template.element, template.name)
51
+ end
52
+
53
+ def key(element, name)
54
+ [element, name]
55
+ end
56
+ end
57
+ end
@@ -1,13 +1,3 @@
1
1
  module Osheet
2
- module Version
3
-
4
- MAJOR = 0
5
- MINOR = 1
6
- TINY = 0
7
-
8
- def self.to_s # :nodoc:
9
- [MAJOR, MINOR, TINY].join('.')
10
- end
11
-
12
- end
2
+ VERSION = "0.2.0"
13
3
  end
@@ -1,23 +1,39 @@
1
- require 'osheet/base'
1
+ require 'osheet/style_set'
2
+ require 'osheet/template_set'
2
3
  require 'osheet/worksheet'
3
-
4
- # === Usage
5
- #
6
- # require 'osheet'
7
- #
8
- # Osheet::Workbook.configure do |book|
9
- # book.worksheet ...
10
- # book.worksheet ...
11
- # end
4
+ require 'osheet/xmlss_writer'
12
5
 
13
6
  module Osheet
14
- class Workbook < Osheet::Base
15
-
16
- has :worksheets => "Worksheet"
7
+ class Workbook
8
+ include Associations
9
+
10
+ hm :worksheets
11
+ attr_reader :styles, :templates
12
+
13
+ def initialize(&block)
14
+ @title = nil
15
+ @styles = StyleSet.new
16
+ @templates = TemplateSet.new
17
+ instance_eval(&block) if block_given?
18
+ end
19
+
20
+ def title(value=nil)
21
+ !value.nil? ? @title = value : @title
22
+ end
23
+ def style(*selectors, &block); @styles << Style.new(*selectors, &block); end
24
+ def template(element, name, &block); @templates << Template.new(element, name, &block); end
17
25
 
18
- def initialize(args={})
19
- super(args)
26
+ def attributes
27
+ { :title => @title }
20
28
  end
21
-
29
+
30
+ def writer
31
+ XmlssWriter::Base.new(:workbook => self)
32
+ end
33
+
34
+ [:to_data, :to_file].each do |meth|
35
+ define_method(meth) {|*args| writer.send(meth, *args) }
36
+ end
37
+
22
38
  end
23
39
  end
@@ -0,0 +1,21 @@
1
+ module Osheet::WorkbookElement
2
+ class << self
3
+
4
+ def included(receiver)
5
+
6
+ receiver.send(:attr_reader, :workbook)
7
+
8
+ [:styles, :templates].each do |thing|
9
+ receiver.send(:define_method, thing) do
10
+ if workbook && workbook.respond_to?(thing)
11
+ workbook.send(thing)
12
+ else
13
+ nil
14
+ end
15
+ end
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+ end
@@ -1,29 +1,27 @@
1
- require 'osheet/base'
2
1
  require 'osheet/column'
3
2
  require 'osheet/row'
4
3
 
5
- # === Usage
6
- #
7
- # require 'osheet'
8
- #
9
- # Osheet::Workbook.configure do |book|
10
- # book.worksheet ...
11
- # book.worksheet ...
12
- # end
13
-
14
4
  module Osheet
15
- class Worksheet < Osheet::Base
16
-
17
- attr_reader :name
5
+ class Worksheet
6
+ include Associations
7
+ include WorkbookElement
8
+
9
+ hm :columns
10
+ hm :rows
11
+
12
+ def initialize(workbook=nil, *args, &block)
13
+ @workbook = workbook
14
+ @name = nil
15
+ instance_exec(*args, &block) if block_given?
16
+ end
18
17
 
19
- needs :workbook
20
- has :columns => "Column"
21
- has :rows => "Row"
18
+ def name(value=nil)
19
+ !value.nil? ? @name = value : @name
20
+ end
22
21
 
23
- def initialize(name=nil, args={})
24
- @name = name
25
- super(args)
22
+ def attributes
23
+ { :name => @name }
26
24
  end
27
-
25
+
28
26
  end
29
27
  end
@@ -0,0 +1,15 @@
1
+ module Osheet::WorksheetElement
2
+ class << self
3
+ def included(receiver)
4
+ receiver.send(:attr_reader, :worksheet)
5
+ end
6
+ end
7
+
8
+ def columns
9
+ if worksheet && worksheet.respond_to?(:columns)
10
+ worksheet.columns
11
+ else
12
+ nil
13
+ end
14
+ end
15
+ end