axlsx 1.2.3 → 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/.yardopts +3 -2
  2. data/CHANGELOG.md +34 -1
  3. data/README.md +26 -37
  4. data/Rakefile +1 -1
  5. data/examples/auto_filter.rb +16 -0
  6. data/examples/auto_filter.xlsx +0 -0
  7. data/examples/example.rb +3 -2
  8. data/examples/example.xlsx +0 -0
  9. data/examples/example_streamed.xlsx +0 -0
  10. data/examples/no-use_autowidth.xlsx +0 -0
  11. data/examples/shared_strings_example.xlsx +0 -0
  12. data/examples/skydrive/real_example.rb +6 -6
  13. data/examples/sprk2012/Screen Shot 2012-09-11 at 10.42.06 PM.png +0 -0
  14. data/examples/sprk2012/Screen Shot 2012-09-11 at 11.07.48 PM.png +0 -0
  15. data/examples/sprk2012/Screen Shot 2012-09-11 at 8.31.50 PM.png +0 -0
  16. data/examples/sprk2012/Screen Shot 2012-09-11 at 9.23.27 PM.png +0 -0
  17. data/examples/sprk2012/Screen Shot 2012-09-11 at 9.32.06 PM.png +0 -0
  18. data/examples/sprk2012/Screen Shot 2012-09-11 at 9.33.35 PM.png +0 -0
  19. data/examples/sprk2012/Screen Shot 2012-09-11 at 9.46.44 PM.png +0 -0
  20. data/examples/sprk2012/Screen Shot 2012-09-12 at 5.07.23 PM.png +0 -0
  21. data/examples/sprk2012/basics.rb +1 -0
  22. data/examples/sprk2012/basics.xlsx +0 -0
  23. data/examples/sprk2012/gravatar.jpeg +0 -0
  24. data/examples/sprk2012/hair_club.jpg +0 -0
  25. data/examples/sprk2012/images.rb +7 -12
  26. data/examples/sprk2012/images.xlsx +0 -0
  27. data/examples/sprk2012/line_chart.rb +56 -0
  28. data/examples/sprk2012/line_chart.xlsx +0 -0
  29. data/examples/sprk2012/sprk2012.key +0 -0
  30. data/examples/sprk2012/styles.rb +13 -12
  31. data/examples/sprk2012/styles.xlsx +0 -0
  32. data/examples/styles.rb +62 -0
  33. data/examples/styles.xlsx +0 -0
  34. data/lib/axlsx.rb +8 -1
  35. data/lib/axlsx/stylesheet/styles.rb +4 -0
  36. data/lib/axlsx/util/constants.rb +90 -5
  37. data/lib/axlsx/util/validators.rb +26 -8
  38. data/lib/axlsx/version.rb +2 -2
  39. data/lib/axlsx/workbook/workbook.rb +4 -1
  40. data/lib/axlsx/workbook/worksheet/auto_filter/auto_filter.rb +77 -0
  41. data/lib/axlsx/workbook/worksheet/auto_filter/filter_column.rb +102 -0
  42. data/lib/axlsx/workbook/worksheet/auto_filter/filters.rb +253 -0
  43. data/lib/axlsx/workbook/worksheet/cell.rb +9 -4
  44. data/lib/axlsx/workbook/worksheet/date_time_converter.rb +1 -1
  45. data/lib/axlsx/workbook/worksheet/page_set_up_pr.rb +47 -0
  46. data/lib/axlsx/workbook/worksheet/sheet_calc_pr.rb +49 -0
  47. data/lib/axlsx/workbook/worksheet/sheet_pr.rb +87 -4
  48. data/lib/axlsx/workbook/worksheet/table.rb +8 -1
  49. data/lib/axlsx/workbook/worksheet/table_style_info.rb +68 -0
  50. data/lib/axlsx/workbook/worksheet/worksheet.rb +18 -3
  51. data/test/stylesheet/tc_styles.rb +13 -0
  52. data/test/util/tc_validators.rb +8 -1
  53. data/test/workbook/worksheet/auto_filter/tc_auto_filter.rb +38 -0
  54. data/test/workbook/worksheet/auto_filter/tc_filter_column.rb +76 -0
  55. data/test/workbook/worksheet/auto_filter/tc_filters.rb +50 -0
  56. data/test/workbook/worksheet/tc_cell.rb +5 -0
  57. data/test/workbook/worksheet/tc_page_set_up_pr.rb +15 -0
  58. data/test/workbook/worksheet/tc_sheet_calc_pr.rb +18 -0
  59. data/test/workbook/worksheet/tc_sheet_pr.rb +27 -0
  60. data/test/workbook/worksheet/{table/tc_table.rb → tc_table.rb} +6 -1
  61. data/test/workbook/worksheet/tc_table_style_info.rb +53 -0
  62. data/test/workbook/worksheet/tc_worksheet.rb +17 -3
  63. metadata +45 -7
  64. data/examples/~$extractive.xlsx +0 -0
  65. data/lib/axlsx/workbook/worksheet/auto_filter.rb +0 -35
@@ -0,0 +1,253 @@
1
+ module Axlsx
2
+
3
+ # When multiple values are chosen to filter by, or when a group of date values are chosen to filter by,
4
+ # this object groups those criteria together.
5
+ class Filters
6
+
7
+ # Allowed calendar types
8
+ CALENDAR_TYPES = %w(gregorian gregorianUs gregorianMeFrench gregorianArabic hijri hebrew taiwan japan thai korea saka gregorianXlitEnglish gregorianXlitFrench none)
9
+
10
+ # Creates a new Filters object
11
+ # @param [Hash] options Options used to set this objects attributes and
12
+ # create filter and/or date group items
13
+ # @option [Boolean] blank @see blank
14
+ # @option [String] calendar_type @see calendar_type
15
+ # @option [Array] filter_items An array of values that will be used to create filter objects.
16
+ # @option [Array] date_group_items An array of hases defining date group item filters to apply.
17
+ # @note The recommended way to interact with filter objects is via AutoFilter#add_column
18
+ # @example
19
+ # ws.auto_filter.add_column(0, :filters, :blank => true, :calendar_type => 'japan', :filter_items => [100, 'a'])
20
+ def initialize(options={})
21
+ options.each do |key, value|
22
+ self.send("#{key}=", value) if self.respond_to? "#{key}="
23
+ end
24
+ end
25
+
26
+ # Flag indicating whether to filter by blank.
27
+ # @return [Boolean]
28
+ attr_reader :blank
29
+
30
+ # Calendar type for date grouped items.
31
+ # Used to interpret the values in dateGroupItem.
32
+ # This is the calendar type used to evaluate all dates in the filter column,
33
+ # even when those dates are not using the same calendar system / date formatting.
34
+ attr_reader :calendar_type
35
+
36
+ # Tells us if the row of the cell provided should be filterd as it
37
+ # does not meet any of the specified filter_items or
38
+ # date_group_items restrictions.
39
+ # @param [Cell] cell The cell to test against items
40
+ # TODO implement this for date filters as well!
41
+ def apply(cell)
42
+ return false unless cell
43
+ filter_items.each do |filter|
44
+ return false if cell.value == filter.val
45
+ end
46
+ true
47
+ end
48
+
49
+ # The filter values in this filters object
50
+ def filter_items
51
+ @filter_items ||= []
52
+ end
53
+
54
+ # the date group values in this filters object
55
+ def date_group_items
56
+ @date_group_items ||= []
57
+ end
58
+
59
+ # @see calendar_type
60
+ # @param [String] calendar The calendar type to use. This must be one of the types defined in CALENDAR_TYPES
61
+ # @return [String]
62
+ def calendar_type=(calendar)
63
+ RestrictionValidator.validate 'Filters.calendar_type', CALENDAR_TYPES, calendar
64
+ @calendar_type = calendar
65
+ end
66
+
67
+ # Set the value for blank
68
+ # @see blank
69
+ def blank=(use_blank)
70
+ Axlsx.validate_boolean use_blank
71
+ @blank = use_blank
72
+ end
73
+
74
+ # Serialize the object to xml
75
+ def to_xml_string(str = '')
76
+ str << "<filters #{serialized_attributes}>"
77
+ filter_items.each { |filter| filter.to_xml_string(str) }
78
+ date_group_items.each { |date_group_item| date_group_item.to_xml_string(str) }
79
+ str << '</filters>'
80
+ end
81
+
82
+ # not entirely happy with this.
83
+ # filter_items should be a simple typed list that overrides << etc
84
+ # to create Filter objects from the inserted values. However this
85
+ # is most likely so rarely used...(really? do you know that?)
86
+ def filter_items=(values)
87
+ values.each do |value|
88
+ filter_items << Filter.new(value)
89
+ end
90
+ end
91
+
92
+ # Date group items are date group filter items where you specify the
93
+ # date_group and a value for that option as part of the auto_filter
94
+ # @note This can be specified, but will not be applied to the date
95
+ # values in your workbook at this time.
96
+ def date_group_items=(options)
97
+ options.each do |date_group|
98
+ raise ArgumentError, "date_group_items should be an array of hashes specifying the options for each date_group_item" unless date_group.is_a?(Hash)
99
+ date_group_items << DateGroupItem.new(date_group)
100
+ end
101
+ end
102
+
103
+ private
104
+
105
+ def serialized_attributes(str='')
106
+ instance_values.each do |key, value|
107
+ if %(blank claendar_type).include? key.to_s
108
+ str << "#{Axlsx.camel(key, false)}='#{value}' "
109
+ end
110
+ end
111
+ str
112
+ end
113
+
114
+ # This class expresses a filter criteria value.
115
+ class Filter
116
+
117
+ # Creates a new filter value object
118
+ # @param [Any] value The value of the filter. This is not restricted, but
119
+ # will be serialized via to_s so if you are passing an object
120
+ # be careful.
121
+ def initialize(value)
122
+ @val = value
123
+ end
124
+
125
+
126
+ #Filter value used in the criteria.
127
+ attr_accessor :val
128
+
129
+ # Serializes the filter value object
130
+ # @param [String] str The string to concact the serialization information to.
131
+ def to_xml_string(str = '')
132
+ str << "<filter val='#{@val.to_s}' />"
133
+ end
134
+ end
135
+
136
+
137
+ # This collection is used to express a group of dates or times which are
138
+ # used in an AutoFilter criteria. Values are always written in the calendar
139
+ # type of the first date encountered in the filter range, so that all
140
+ # subsequent dates, even when formatted or represented by other calendar
141
+ # types, can be correctly compared for the purposes of filtering.
142
+ class DateGroupItem
143
+
144
+ # Allowed date time groupings
145
+ DATE_TIME_GROUPING = %w(year month day hour minute second)
146
+
147
+ # Creates a new DateGroupItem
148
+ # @param [Hash] options A hash of options to use when
149
+ # instanciating the object
150
+ # @option [String] date_time_grouping the part of the date this
151
+ # filter should apply for grouping
152
+ # @option [Integer|String] year @see year
153
+ # @option [Integer] month @see month
154
+ # @option [Integer] day @see day
155
+ # @option [Integer] hour @see hour
156
+ # @option [Integer] minute @see minute
157
+ # @option [Integer] second @see second
158
+ def initialize(options={})
159
+ raise ArgumentError, "You must specify a year for date time grouping" unless options[:year]
160
+ raise ArgumentError, "You must specify a date_time_grouping when creating a DateGroupItem for auto filter" unless options[:date_time_grouping]
161
+ options.each do |key, value|
162
+ self.send("#{key}=", value) if self.respond_to?("#{key}=")
163
+ end
164
+ end
165
+
166
+ # Grouping level
167
+ # This must be one of year, month, day, hour, minute or second.
168
+ # @return [String]
169
+ attr_reader :date_time_grouping
170
+
171
+ # Year (4 digits)
172
+ # @return [Integer|String]
173
+ attr_reader :year
174
+
175
+ # Month (1..12)
176
+ # @return [Integer]
177
+ attr_reader :month
178
+
179
+ # Day (1-31)
180
+ # @return [Integer]
181
+ attr_reader :day
182
+
183
+ # Hour (0..23)
184
+ # @return [Integer]
185
+ attr_reader :hour
186
+
187
+ # Minute (0..59(
188
+ # @return [Integer]
189
+ attr_reader :minute
190
+
191
+ # Second (0..59)
192
+ # @return [Integer]
193
+ attr_reader :second
194
+
195
+ # The year value for the date group item
196
+ # This must be a four digit value
197
+ def year=(value)
198
+ RegexValidator.validate "DateGroupItem.year", /\d{4}/, value
199
+ @year = value
200
+ end
201
+
202
+ # The month value for the date group item
203
+ # This must be between 1 and 12
204
+ def month=(value)
205
+ RangeValidator.validate "DateGroupItem.month", 0, 12, value
206
+ @month = value
207
+ end
208
+
209
+ # The day value for the date group item
210
+ # This must be between 1 and 31
211
+ # @note no attempt is made to ensure the date value is valid for any given month
212
+ def day=(value)
213
+ RangeValidator.validate "DateGroupItem.day", 0, 31, value
214
+ @day = value
215
+ end
216
+
217
+ # The hour value for the date group item
218
+ # # this must be between 0 and 23
219
+ def hour=(value)
220
+ RangeValidator.validate "DateGroupItem.hour", 0, 23, value
221
+ @hour = value
222
+ end
223
+
224
+ # The minute value for the date group item
225
+ # This must be between 0 and 59
226
+ def minute=(value)
227
+ RangeValidator.validate "DateGroupItem.minute", 0, 59, value
228
+ @minute = value
229
+ end
230
+
231
+ # The second value for the date group item
232
+ # This must be between 0 and 59
233
+ def second=(value)
234
+ RangeValidator.validate "DateGroupItem.second", 0, 59, value
235
+ @second = value
236
+ end
237
+
238
+ # The date time grouping for this filter.
239
+ def date_time_grouping=(grouping)
240
+ RestrictionValidator.validate 'DateGroupItem.date_time_grouping', DATE_TIME_GROUPING, grouping.to_s
241
+ @date_time_grouping = grouping.to_s
242
+ end
243
+
244
+ # Serialize the object to xml
245
+ # @param [String] str The string object this serialization will be concatenated to.
246
+ def to_xml_string(str = '')
247
+ str << '<dateGroupItem '
248
+ instance_values.each { |key, value| str << "#{key}='#{value.to_s}' " }
249
+ str << '/>'
250
+ end
251
+ end
252
+ end
253
+ end
@@ -330,7 +330,7 @@ module Axlsx
330
330
  font_scale = (font_size/10.0).to_f
331
331
  ((value.to_s.count(Worksheet.thin_chars) * mdw + 5) / mdw * 256) / 256.0 * font_scale
332
332
  end
333
-
333
+
334
334
  # returns the absolute or relative string style reference for
335
335
  # this cell.
336
336
  # @param [Boolean] absolute -when false a relative reference will be
@@ -338,12 +338,17 @@ module Axlsx
338
338
  # @return [String]
339
339
  def reference(absolute=true)
340
340
  absolute ? r_abs : r
341
- end
342
-
341
+ end
342
+
343
343
  private
344
344
 
345
+ # we scale the font size if bold style is applied to either the style font or
346
+ # the cell itself. Yes, it is a bit of a hack, but it is much better than using
347
+ # imagemagick and loading metrics for every character.
345
348
  def font_size
346
- sz || @styles.fonts[@styles.cellXfs[style].fontId].sz || @styles.fonts[0].sz
349
+ font = @styles.fonts[@styles.cellXfs[style].fontId] || @styles.fonts[0]
350
+ size_from_styles = (font.b || b) ? font.sz * 1.5 : font.sz
351
+ sz || size_from_styles
347
352
  end
348
353
 
349
354
  # Utility method for setting inline style attributes
@@ -10,7 +10,7 @@ module Axlsx
10
10
  # @return [Numeric]
11
11
  def self.date_to_serial(date)
12
12
  epoch = Axlsx::Workbook::date1904 ? Date.new(1904) : Date.new(1899, 12, 30)
13
- (date-epoch).to_f
13
+ (date - epoch).to_f
14
14
  end
15
15
 
16
16
  # The time_to_serial methond converts a Time object its excel serialized form.
@@ -0,0 +1,47 @@
1
+ module Axlsx
2
+
3
+ # Page setup properties of the worksheet
4
+ # This class name is not a typo, its spec.
5
+ class PageSetUpPr
6
+
7
+ # creates a new page setup properties object
8
+ # @param [Hash] options
9
+ # @option [Boolean] auto_page_breaks Flag indicating whether the sheet displays Automatic Page Breaks.
10
+ # @option [Boolean] fit_to_page Flag indicating whether the Fit to Page print option is enabled.
11
+ def initialize(options = {})
12
+ options.each do |key, value|
13
+ self.send("#{key}=",value) if self.respond_to?("#{key}=")
14
+ end
15
+ end
16
+
17
+ attr_reader :auto_page_breaks
18
+ attr_reader :fit_to_page
19
+
20
+ # Flag indicating whether the Fit to Page print option is enabled.
21
+ # @param [Boolean] value
22
+ # @return [Boolean]
23
+ def fit_to_page=(value)
24
+ Axlsx.validate_boolean value
25
+ @fit_to_page = value
26
+ end
27
+
28
+ # Flag indicating whether the sheet displays Automatic Page Breaks.
29
+ # @param [Boolean] value
30
+ # @return [Boolean]
31
+ def auto_page_breaks=(value)
32
+ Axlsx.validate_boolean value
33
+ @auto_page_breaks = value
34
+ end
35
+
36
+ # serialize to xml
37
+ def to_xml_string(str='')
38
+ str << '<pageSetUpPr ' << serialized_attributes << '/>'
39
+ end
40
+
41
+ private
42
+
43
+ def serialized_attributes
44
+ instance_values.map { |key, value| "#{Axlsx.camel(key, false)}='#{value}'" }.join(' ')
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,49 @@
1
+ module Axlsx
2
+
3
+ # the SheetCalcPr object for the worksheet
4
+ # This object contains calculation properties for the worksheet.
5
+ class SheetCalcPr
6
+
7
+ # creates a new SheetCalcPr
8
+ # @param [Hash] options Options for this object
9
+ # @option [Boolean] full_calc_on_load @see full_calc_on_load
10
+ def initialize(options={})
11
+ @full_calc_on_load = true
12
+ options.each do |key, value|
13
+ self.send("#{key}=", value) if self.respond_to?("#{key}=")
14
+ end
15
+ end
16
+
17
+ # Indicates whether the application should do a full calculate on
18
+ # load due to contents on this sheet. After load and successful cal,c
19
+ # the application shall set this value to false. Set this to true
20
+ # when the application should calculate the workbook on load.
21
+ # @return [Boolean]
22
+ def full_calc_on_load
23
+ @full_calc_on_load
24
+ end
25
+
26
+ # specify the full_calc_on_load value
27
+ # @param [Boolean] value
28
+ # @see full_calc_on_load
29
+ def full_calc_on_load=(value)
30
+ Axlsx.validate_boolean(value)
31
+ @full_calc_on_load = value
32
+ end
33
+
34
+ # Serialize the object
35
+ # @param [String] str the string to append this objects serialized
36
+ # content to.
37
+ # @return [String]
38
+ def to_xml_string(str='')
39
+ str << "<sheetCalcPr #{serialized_attributes}/>"
40
+ end
41
+
42
+ private
43
+
44
+ def serialized_attributes
45
+ instance_values.map { |key, value| "#{Axlsx.camel(key, false)}='#{value}'" }.join(' ')
46
+ end
47
+
48
+ end
49
+ end
@@ -1,24 +1,107 @@
1
1
  module Axlsx
2
2
 
3
3
  # The SheetPr class manages serialization fo a worksheet's sheetPr element.
4
- # Only fit_to_page is implemented
5
4
  class SheetPr
6
5
 
6
+
7
+ # These attributes are all boolean so I'm doing a bit of a hand
8
+ # waving magic show to set up the attriubte accessors
9
+ BOOLEAN_ATTRIBUTES = [:sync_horizontal,
10
+ :sync_vertical,
11
+ :transtion_evaluation,
12
+ :transition_entry,
13
+ :published,
14
+ :filter_mode,
15
+ :enable_format_conditions_calculation]
16
+
17
+
7
18
  # Creates a new SheetPr object
8
19
  # @param [Worksheet] worksheet The worksheet that owns this SheetPr object
9
- def initialize(worksheet)
20
+ def initialize(worksheet, options={})
10
21
  raise ArgumentError, "you must provide a worksheet" unless worksheet.is_a?(Worksheet)
11
22
  @worksheet = worksheet
23
+ options.each do |key, value|
24
+ attr = "#{key}="
25
+ self.send(attr, value) if self.respond_to?(attr)
26
+ end
27
+ end
28
+
29
+ # Dynamically create accessors for boolean attriubtes
30
+ BOOLEAN_ATTRIBUTES.each do |attr|
31
+ class_eval %{
32
+ # The #{attr} attribute reader
33
+ # @return [Boolean]
34
+ attr_reader :#{attr}
35
+
36
+ # The #{attr} writer
37
+ # @param [Boolean] value The value to assign to #{attr}
38
+ # @return [Boolean]
39
+ def #{attr}=(value)
40
+ Axlsx::validate_boolean(value)
41
+ @#{attr} = value
42
+ end
43
+ }
12
44
  end
13
45
 
46
+ # Anchor point for worksheet's window.
47
+ # @return [String]
48
+ attr_reader :code_name
49
+
50
+ # Specifies a stable name of the sheet, which should not change over time,
51
+ # and does not change from user input. This name should be used by code
52
+ # to reference a particular sheet.
53
+ # @return [String]
54
+ attr_reader :sync_ref
55
+
56
+ # The worksheet these properties apply to!
57
+ # @return [Worksheet]
14
58
  attr_reader :worksheet
15
59
 
60
+ # @see code_name
61
+ # @param [String] name
62
+ def code_name=(name)
63
+ @code_name = name
64
+ end
65
+
66
+ # @see sync_ref
67
+ # @param [String] ref A cell reference (e.g. "A1")
68
+ def sync_ref=(ref)
69
+ @sync_ref = ref
70
+ end
71
+
16
72
  # Serialize the object
17
73
  # @param [String] str serialized output will be appended to this object if provided.
18
74
  # @return [String]
19
75
  def to_xml_string(str = '')
20
- return unless worksheet.fit_to_page?
21
- str << "<sheetPr><pageSetUpPr fitToPage=\"%s\"></pageSetUpPr></sheetPr>" % worksheet.fit_to_page?
76
+ update_properties
77
+ str << "<sheetPr #{serialized_attributes}>"
78
+ page_setup_pr.to_xml_string(str)
79
+ str << "</sheetPr>"
80
+ end
81
+
82
+ # The PageSetUpPr for this sheet pr object
83
+ # @return [PageSetUpPr]
84
+ def page_setup_pr
85
+ @page_setup_pr ||= PageSetUpPr.new
86
+ end
87
+
88
+ private
89
+
90
+ def serialized_attributes(str = '')
91
+ instance_values.each do |key, value|
92
+ unless %(worksheet page_setup_pr).include? key
93
+ str << "#{Axlsx.camel(key, false)}='#{value}' "
94
+ end
95
+ end
96
+ str
97
+ end
98
+
99
+ def update_properties
100
+ page_setup_pr.fit_to_page = worksheet.fit_to_page?
101
+ if worksheet.auto_filter.columns.size > 0
102
+ self.filter_mode = 1
103
+ self.enable_format_conditions_calculation = 1
104
+ end
22
105
  end
23
106
  end
24
107
  end