workbook 0.4.6.0 → 0.4.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/README.md +14 -15
  4. data/lib/workbook.rb +22 -11
  5. data/lib/workbook/book.rb +47 -25
  6. data/lib/workbook/cell.rb +20 -26
  7. data/lib/workbook/generatetypes.rb +14 -0
  8. data/lib/workbook/modules/cache.rb +52 -0
  9. data/lib/workbook/modules/{table_diff_sort.rb → diff_sort.rb} +64 -16
  10. data/lib/workbook/modules/raw_objects_storage.rb +7 -2
  11. data/lib/workbook/readers/ods_reader.rb +1 -1
  12. data/lib/workbook/readers/xls_reader.rb +55 -55
  13. data/lib/workbook/readers/xls_shared.rb +47 -0
  14. data/lib/workbook/readers/xlsx_reader.rb +34 -153
  15. data/lib/workbook/row.rb +47 -4
  16. data/lib/workbook/sheet.rb +4 -0
  17. data/lib/workbook/table.rb +36 -16
  18. data/lib/workbook/types/Date.rb +9 -0
  19. data/lib/workbook/types/False.rb +0 -0
  20. data/lib/workbook/types/FalseClass.rb +9 -0
  21. data/lib/workbook/types/Nil.rb +0 -0
  22. data/lib/workbook/types/NilClass.rb +9 -0
  23. data/lib/workbook/types/Numeric.rb +9 -0
  24. data/lib/workbook/types/String.rb +9 -0
  25. data/lib/workbook/types/Time.rb +9 -0
  26. data/lib/workbook/types/True.rb +0 -0
  27. data/lib/workbook/types/TrueClass.rb +9 -0
  28. data/lib/workbook/version.rb +1 -1
  29. data/lib/workbook/writers/html_writer.rb +40 -18
  30. data/lib/workbook/writers/xls_writer.rb +47 -5
  31. data/lib/workbook/writers/xlsx_writer.rb +123 -0
  32. data/test/artifacts/bigtable.xls +0 -0
  33. data/test/artifacts/bigtable.xlsx +0 -0
  34. data/test/artifacts/simple_sheet.xlsx +0 -0
  35. data/test/artifacts/simple_sheet_many_sheets.xls +0 -0
  36. data/test/test_book.rb +50 -2
  37. data/test/test_cell.rb +1 -1
  38. data/test/test_format.rb +8 -0
  39. data/test/test_modules_cache.rb +68 -0
  40. data/test/test_modules_table_diff_sort.rb +12 -1
  41. data/test/test_readers_xls_reader.rb +6 -0
  42. data/test/test_readers_xlsx_reader.rb +10 -9
  43. data/test/test_row.rb +65 -8
  44. data/test/test_sheet.rb +8 -0
  45. data/test/test_table.rb +48 -0
  46. data/test/test_writers_html_writer.rb +18 -8
  47. data/test/test_writers_xls_writer.rb +90 -0
  48. data/test/test_writers_xlsx_writer.rb +153 -0
  49. data/workbook.gemspec +9 -7
  50. metadata +71 -31
@@ -34,6 +34,10 @@ module Workbook
34
34
  first
35
35
  end
36
36
 
37
+ def name
38
+ @name ||= "Sheet #{book.index(self)+1}"
39
+ end
40
+
37
41
  # Set the first table of this sheet with a table or array of cells/values
38
42
  # @param [Workbook::Table, Array<Array>] table The new first table of this sheet
39
43
  # @param [Hash] options are forwarded to Workbook::Table.new
@@ -1,5 +1,5 @@
1
1
  # -*- encoding : utf-8 -*-
2
- require 'workbook/modules/table_diff_sort'
2
+ require 'workbook/modules/diff_sort'
3
3
  require 'workbook/writers/csv_table_writer'
4
4
  require 'workbook/writers/json_table_writer'
5
5
  require 'workbook/writers/html_writer'
@@ -29,6 +29,13 @@ module Workbook
29
29
  # Column data is considered as a 'row' with 'cells' that contain 'formatting'
30
30
  end
31
31
 
32
+ # Quick assessor to the book's template, if it exists
33
+ #
34
+ # @return [Workbook::Template]
35
+ def template
36
+ sheet.book.template
37
+ end
38
+
32
39
  # Returns the header of this table (typically the first row, but can be a different row).
33
40
  # The header row is also used for finding values in a aribrary row.
34
41
  #
@@ -86,18 +93,6 @@ module Workbook
86
93
  row.set_table(self)
87
94
  end
88
95
 
89
- # Overrides normal Row's []=-function; automatically converting to row and setting
90
- # with the label correctly
91
- #
92
- # @param [Fixnum] index
93
- # @param [Workbook::Table, Array] row to set
94
- # @return [Workbook::Cell, nil]
95
- def []= (index, row)
96
- row = Workbook::Row.new(row) if row.class == Array
97
- super(index,row)
98
- row.set_table(self)
99
- end
100
-
101
96
  def has_contents?
102
97
  self.clone.remove_empty_lines!.count != 0
103
98
  end
@@ -157,10 +152,35 @@ module Workbook
157
152
  cell_index = alpha_index_to_number_index(match[1])
158
153
  row_index = match[2].to_i - 1
159
154
  return self[row_index][cell_index]
155
+ elsif index_or_string.is_a? Range
156
+ collection = to_a[index_or_string].collect{|a| a.clone}
157
+ return Workbook::Table.new collection
158
+ elsif index_or_string.is_a? Integer
159
+ return to_a[index_or_string]
160
+ end
161
+ end
162
+
163
+ # Overrides normal Row's []=-function; automatically converting to row and setting
164
+ # with the label correctly
165
+ #
166
+ # @example Lookup using fixnum or header value encoded as symbol
167
+ # `table[0] = <Row [a,2,3,4]>` (set first row)
168
+ # `table["A1"] = 2` (set first cell of first row to 2)
169
+ #
170
+ # @param [Fixnum, String] index_or_string to reference to either the row, or the cell
171
+ # @param [Workbook::Table, Array] new_value to set
172
+ # @return [Workbook::Cell, nil]
173
+ def []= (index_or_string, new_value)
174
+ if index_or_string.is_a? String
175
+ match = index_or_string.upcase.match(/([A-Z]*)([0-9]*)/)
176
+ cell_index = alpha_index_to_number_index(match[1])
177
+ row_index = match[2].to_i - 1
178
+ self[row_index][cell_index].value = new_value
160
179
  else
161
- if index_or_string
162
- return to_a[index_or_string]
163
- end
180
+ row = new_value
181
+ row = Workbook::Row.new(row) unless row.is_a? Workbook::Row
182
+ super(index_or_string,row)
183
+ row.set_table(self)
164
184
  end
165
185
  end
166
186
 
@@ -0,0 +1,9 @@
1
+ require 'workbook/cell'
2
+
3
+ module Workbook
4
+ module Types
5
+ class Date < Date
6
+ include Workbook::Cell
7
+ end
8
+ end
9
+ end
File without changes
@@ -0,0 +1,9 @@
1
+ require 'workbook/cell'
2
+
3
+ module Workbook
4
+ module Types
5
+ class FalseClass < FalseClass
6
+ include Workbook::Cell
7
+ end
8
+ end
9
+ end
File without changes
@@ -0,0 +1,9 @@
1
+ require 'workbook/cell'
2
+
3
+ module Workbook
4
+ module Types
5
+ class NilClass < NilClass
6
+ include Workbook::Cell
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ require 'workbook/cell'
2
+
3
+ module Workbook
4
+ module Types
5
+ class Numeric < Numeric
6
+ include Workbook::Cell
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ require 'workbook/cell'
2
+
3
+ module Workbook
4
+ module Types
5
+ class String < String
6
+ include Workbook::Cell
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ require 'workbook/cell'
2
+
3
+ module Workbook
4
+ module Types
5
+ class Time < Time
6
+ include Workbook::Cell
7
+ end
8
+ end
9
+ end
File without changes
@@ -0,0 +1,9 @@
1
+ require 'workbook/cell'
2
+
3
+ module Workbook
4
+ module Types
5
+ class TrueClass < TrueClass
6
+ include Workbook::Cell
7
+ end
8
+ end
9
+ end
@@ -1,4 +1,4 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
  module Workbook
3
- VERSION = '0.4.6.0'
3
+ VERSION = '0.4.7'
4
4
  end
@@ -47,28 +47,50 @@ module Workbook
47
47
  # Generates an HTML table ()
48
48
  #
49
49
  # @param [Hash] options A hash with options
50
- # @return [String] A String containing the HTML code
50
+ # @return [String] A String containing the HTML code, most importantly `:style_with_inline_css` (default false)
51
51
  def to_html options={}
52
52
  options = {:style_with_inline_css=>false}.merge(options)
53
53
  builder = Nokogiri::XML::Builder.new do |doc|
54
- doc.table {
55
- self.each{|row|
56
- doc.tr {
57
- row.each {|cell|
58
- classnames = cell.format.all_names.join(" ").strip
59
- td_options = classnames != "" ? {:class=>classnames} : {}
60
- td_options = td_options.merge({:style=>cell.format.to_css}) if options[:style_with_inline_css] and cell.format.to_css != ""
61
- td_options = td_options.merge({:colspan=>cell.colspan}) if cell.colspan
62
- td_options = td_options.merge({:rowspan=>cell.rowspan}) if cell.rowspan
63
- unless cell.value.class == Workbook::NilValue
64
- doc.td(td_options) {
65
- doc.text cell.value
66
- }
54
+ doc.table do
55
+ doc.thead do
56
+ if header
57
+ doc.tr do
58
+ header.each do |cell|
59
+ classnames = cell.format.all_names.join(" ").strip
60
+ td_options = classnames != "" ? {:class=>classnames} : {}
61
+ td_options = td_options.merge({:style=>cell.format.to_css}) if options[:style_with_inline_css] and cell.format.to_css != ""
62
+ td_options = td_options.merge({:colspan=>cell.colspan}) if cell.colspan
63
+ td_options = td_options.merge({:rowspan=>cell.rowspan}) if cell.rowspan
64
+ unless cell.value.class == Workbook::NilValue
65
+ doc.th(td_options) do
66
+ doc.text cell.value
67
+ end
68
+ end
67
69
  end
68
- }
69
- }
70
- }
71
- }
70
+ end
71
+ end
72
+ end
73
+ doc.tbody do
74
+ self.each do |row|
75
+ unless row.header?
76
+ doc.tr do
77
+ row.each do |cell|
78
+ classnames = cell.format.all_names.join(" ").strip
79
+ td_options = classnames != "" ? {:class=>classnames} : {}
80
+ td_options = td_options.merge({:style=>cell.format.to_css}) if options[:style_with_inline_css] and cell.format.to_css != ""
81
+ td_options = td_options.merge({:colspan=>cell.colspan}) if cell.colspan
82
+ td_options = td_options.merge({:rowspan=>cell.rowspan}) if cell.rowspan
83
+ unless cell.value.class == Workbook::NilValue
84
+ doc.td(td_options) do
85
+ doc.text cell.value
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
72
94
  end
73
95
  return builder.doc.to_xhtml
74
96
  end
@@ -5,15 +5,16 @@ module Workbook
5
5
  module Writers
6
6
  module XlsWriter
7
7
 
8
- # Generates an Spreadsheet (from the spreadsheet gem) in order to build an XlS
8
+ # Generates an Spreadsheet (from the spreadsheet gem) in order to build an xls
9
9
  #
10
10
  # @param [Hash] options A hash with options (unused so far)
11
11
  # @return [Spreadsheet] A Spreadsheet object, ready for writing or more lower level operations
12
12
  def to_xls options={}
13
13
  book = init_spreadsheet_template
14
14
  self.each_with_index do |s,si|
15
- xls_sheet = book.worksheet si
16
- xls_sheet = book.create_worksheet if xls_sheet == nil
15
+ xls_sheet = xls_sheet(si)
16
+ xls_sheet.name = s.name
17
+
17
18
  s.table.each_with_index do |r, ri|
18
19
  xls_sheet.row(ri).height= r.format[:height] if r.format
19
20
  r.each_with_index do |c, ci|
@@ -27,6 +28,23 @@ module Workbook
27
28
  end
28
29
  end
29
30
  end
31
+ (xls_sheet.last_row_index + 1 - s.table.count).times do |time|
32
+ row_to_remove = s.table.count+time
33
+ xls_sheet.row(row_to_remove).each_with_index do |c, ci|
34
+ xls_sheet.row(row_to_remove)[ci]=nil
35
+ end
36
+
37
+ xls_sheet.delete_row(row_to_remove)
38
+ xls_sheet.row_updated(row_to_remove, xls_sheet.row(row_to_remove))
39
+ end
40
+ xls_sheet.updated_from(s.table.count)
41
+ xls_sheet.dimensions
42
+
43
+ end
44
+ # kind of a hack, delting by popping from xls worksheet results in Excel-errors (not LibreOffice)
45
+ # book.worksheets.pop(book.worksheets.count - self.count) if book.worksheets and book.worksheets.count > self.count
46
+ book.worksheets.each_with_index do |sheet, si|
47
+ sheet.visibility = self[si] ? :visible : :strong_hidden
30
48
  end
31
49
  book
32
50
  end
@@ -49,13 +67,37 @@ module Workbook
49
67
  xlsfmt.number_format = strftime_to_ms_format(f[:number_format]) if f[:number_format]
50
68
  xlsfmt.text_direction = f[:text_direction] if f[:text_direction]
51
69
  xlsfmt.font.name = f[:font_family].split.first if f[:font_family]
52
- xlsfmt.font.family = f[:font_family].split.last if f[:font_family]
53
- xlsfmt.font.color = html_color_to_xls_color(f[:color]) if f[:color]
70
+ xlsfmt.font.family = parse_font_family(f) if f[:font_family]
71
+ color = html_color_to_xls_color(f[:color])
72
+ xlsfmt.font.color = color if color
54
73
  f.add_raw xlsfmt
55
74
  end
56
75
  return xlsfmt
57
76
  end
58
77
 
78
+ # Parses right font-family name
79
+ #
80
+ # @param [Workbook::Format, hash] format to parse
81
+ def parse_font_family(format)
82
+ font = format[:font_family].to_s.split.last
83
+ valid_values = [:none,:roman,:swiss,:modern,:script,:decorative]
84
+ if valid_values.include?(font)
85
+ return font
86
+ elsif valid_values.include?(font.to_s.downcase.to_sym)
87
+ return font.to_s.downcase.to_sym
88
+ else
89
+ font = font.to_s.downcase.strip
90
+ translation = {
91
+ "arial"=>:swiss,
92
+ "times"=>:roman,
93
+ "times new roman"=>:roman
94
+ }
95
+ tfont = translation[font]
96
+ return tfont ? tfont : :none
97
+ end
98
+ end
99
+
100
+
59
101
  # Attempt to convert html-hex color value to xls color number
60
102
  #
61
103
  # @param [String] hex color
@@ -0,0 +1,123 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'rubyXL'
3
+ require 'workbook/readers/xls_shared'
4
+
5
+ module Workbook
6
+ module Writers
7
+ module XlsxWriter
8
+
9
+ # Generates an Spreadsheet (from the spreadsheet gem) in order to build an XlS
10
+ #
11
+ # @param [Hash] options A hash with options (unused so far)
12
+ # @return [Spreadsheet] A Spreadsheet object, ready for writing or more lower level operations
13
+ def to_xlsx options={}
14
+ book = init_xlsx_spreadsheet_template
15
+ book.theme = RubyXL::Theme.new unless book.theme #workaround bug in rubyxl
16
+ book.worksheets.pop(book.worksheets.count - self.count) if book.worksheets and book.worksheets.count > self.count
17
+ self.each_with_index do |s,si|
18
+ xls_sheet = xlsx_sheet(si)
19
+ xls_sheet.sheet_name = s.name
20
+
21
+ s.table.each_with_index do |r, ri|
22
+ r.each_with_index do |c, ci|
23
+ xls_sheet.add_cell(ri, ci, c.value)
24
+
25
+ end
26
+ end
27
+ (xls_sheet.count + 1 - s.table.count).times do |time|
28
+ row_to_remove = s.table.count+time
29
+
30
+ xls_sheet.delete_row(row_to_remove)
31
+ # xls_sheet.row_updated(row_to_remove, xls_sheet.row(row_to_remove))
32
+ end
33
+ # xls_sheet.updated_from(s.table.count)
34
+ # xls_sheet.dimensions
35
+
36
+ end
37
+ book
38
+ end
39
+
40
+ # # Generates an Spreadsheet (from the spreadsheet gem) in order to build an XlS
41
+ # #
42
+ # # @param [Workbook::Format, Hash] f A Workbook::Format or hash with format-options (:font_weight, :rotation, :background_color, :number_format, :text_direction, :color, :font_family)
43
+ # # @return [Spreadsheet::Format] A Spreadsheet format-object, ready for writing or more lower level operations
44
+ # def format_to_xlsx_format f
45
+ # xlsfmt = nil
46
+ # unless f.is_a? Workbook::Format
47
+ # f = Workbook::Format.new f
48
+ # end
49
+ # xlsfmt = f.return_raw_for Spreadsheet::Format
50
+ # unless xlsfmt
51
+ # xlsfmt=Spreadsheet::Format.new :weight=>f[:font_weight]
52
+ # xlsfmt.rotation = f[:rotation] if f[:rotation]
53
+ # xlsfmt.pattern_fg_color = html_color_to_xls_color(f[:background_color]) if html_color_to_xls_color(f[:background_color])
54
+ # xlsfmt.pattern = 1 if html_color_to_xls_color(f[:background_color])
55
+ # xlsfmt.number_format = strftime_to_ms_format(f[:number_format]) if f[:number_format]
56
+ # xlsfmt.text_direction = f[:text_direction] if f[:text_direction]
57
+ # xlsfmt.font.name = f[:font_family].split.first if f[:font_family]
58
+ # xlsfmt.font.family = parse_font_family(f) if f[:font_family]
59
+ # color = html_color_to_xls_color(f[:color])
60
+ # xlsfmt.font.color = color if color
61
+ # f.add_raw xlsfmt
62
+ # end
63
+ # return xlsfmt
64
+ # end
65
+ #
66
+ # # Parses right font-family name
67
+ # #
68
+ # # @param [Workbook::Format, hash] format to parse
69
+ # def parse_font_family(format)
70
+ # font = format[:font_family].to_s.split.last
71
+ # valid_values = [:none,:roman,:swiss,:modern,:script,:decorative]
72
+ # if valid_values.include?(font)
73
+ # return font
74
+ # elsif valid_values.include?(font.to_s.downcase.to_sym)
75
+ # return font.to_s.downcase.to_sym
76
+ # else
77
+ # font = font.to_s.downcase.strip
78
+ # translation = {
79
+ # "arial"=>:swiss,
80
+ # "times"=>:roman,
81
+ # "times new roman"=>:roman
82
+ # }
83
+ # tfont = translation[font]
84
+ # return tfont ? tfont : :none
85
+ # end
86
+ # end
87
+ #
88
+
89
+ # Write the current workbook to Microsoft Excel format (using the spreadsheet gem)
90
+ #
91
+ # @param [String] filename
92
+ # @param [Hash] options see #to_xls
93
+ def write_to_xlsx filename="#{title}.xlsx", options={}
94
+ if to_xlsx(options).write(filename)
95
+ return filename
96
+ end
97
+ end
98
+
99
+ def xlsx_sheet a
100
+ if xlsx_template.worksheets[a]
101
+ return xlsx_template.worksheets[a]
102
+ else
103
+ xlsx_template.create_worksheet
104
+ self.xls_sheet a
105
+ end
106
+ end
107
+
108
+ def xlsx_template
109
+ return template.raws[RubyXL::Workbook]
110
+ end
111
+
112
+ def init_xlsx_spreadsheet_template
113
+ if self.xlsx_template.is_a? RubyXL::Workbook
114
+ return self.xlsx_template
115
+ else
116
+ t = RubyXL::Workbook.new
117
+ template.add_raw t
118
+ return t
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end