axlsx 1.3.6 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts_guide +19 -0
  3. data/CHANGELOG.md +8 -0
  4. data/README.md +52 -79
  5. data/Rakefile +0 -5
  6. data/examples/2010_comments.rb +17 -0
  7. data/examples/anchor_swapping.rb +28 -0
  8. data/examples/example.rb +16 -1
  9. data/examples/pivot_table.rb +2 -0
  10. data/examples/underline.rb +13 -0
  11. data/lib/axlsx.rb +8 -0
  12. data/lib/axlsx/doc_props/core.rb +6 -1
  13. data/lib/axlsx/drawing/axes.rb +7 -3
  14. data/lib/axlsx/drawing/bar_3D_chart.rb +2 -2
  15. data/lib/axlsx/drawing/chart.rb +20 -4
  16. data/lib/axlsx/drawing/drawing.rb +2 -17
  17. data/lib/axlsx/drawing/graphic_frame.rb +3 -8
  18. data/lib/axlsx/drawing/hyperlink.rb +5 -12
  19. data/lib/axlsx/drawing/marker.rb +25 -5
  20. data/lib/axlsx/drawing/one_cell_anchor.rb +9 -0
  21. data/lib/axlsx/drawing/pic.rb +17 -23
  22. data/lib/axlsx/drawing/two_cell_anchor.rb +7 -27
  23. data/lib/axlsx/package.rb +31 -11
  24. data/lib/axlsx/rels/relationship.rb +73 -8
  25. data/lib/axlsx/rels/relationships.rb +8 -1
  26. data/lib/axlsx/stylesheet/color.rb +1 -1
  27. data/lib/axlsx/stylesheet/num_fmt.rb +2 -2
  28. data/lib/axlsx/stylesheet/styles.rb +5 -3
  29. data/lib/axlsx/util/serialized_attributes.rb +11 -8
  30. data/lib/axlsx/util/simple_typed_list.rb +34 -13
  31. data/lib/axlsx/util/validators.rb +7 -0
  32. data/lib/axlsx/version.rb +1 -1
  33. data/lib/axlsx/workbook/defined_name.rb +1 -1
  34. data/lib/axlsx/workbook/shared_strings_table.rb +12 -3
  35. data/lib/axlsx/workbook/workbook.rb +31 -8
  36. data/lib/axlsx/workbook/worksheet/break.rb +37 -0
  37. data/lib/axlsx/workbook/worksheet/cell.rb +5 -5
  38. data/lib/axlsx/workbook/worksheet/cell_serializer.rb +1 -1
  39. data/lib/axlsx/workbook/worksheet/col_breaks.rb +35 -0
  40. data/lib/axlsx/workbook/worksheet/comment.rb +6 -5
  41. data/lib/axlsx/workbook/worksheet/comments.rb +3 -3
  42. data/lib/axlsx/workbook/worksheet/conditional_formatting_rule.rb +1 -1
  43. data/lib/axlsx/workbook/worksheet/date_time_converter.rb +6 -5
  44. data/lib/axlsx/workbook/worksheet/pivot_table.rb +32 -18
  45. data/lib/axlsx/workbook/worksheet/pivot_table_cache_definition.rb +4 -3
  46. data/lib/axlsx/workbook/worksheet/pivot_tables.rb +1 -1
  47. data/lib/axlsx/workbook/worksheet/row.rb +1 -1
  48. data/lib/axlsx/workbook/worksheet/row_breaks.rb +33 -0
  49. data/lib/axlsx/workbook/worksheet/table.rb +3 -2
  50. data/lib/axlsx/workbook/worksheet/tables.rb +1 -1
  51. data/lib/axlsx/workbook/worksheet/worksheet.rb +61 -26
  52. data/lib/axlsx/workbook/worksheet/worksheet_comments.rb +6 -5
  53. data/lib/axlsx/workbook/worksheet/worksheet_drawing.rb +3 -9
  54. data/lib/axlsx/workbook/worksheet/worksheet_hyperlink.rb +5 -10
  55. data/lib/schema/sml.xsd +4 -0
  56. data/test/axlsx.qcachegrind +2226 -0
  57. data/test/doc_props/tc_core.rb +7 -0
  58. data/test/drawing/tc_axes.rb +8 -0
  59. data/test/drawing/tc_bar_3D_chart.rb +6 -0
  60. data/test/drawing/tc_chart.rb +13 -0
  61. data/test/drawing/tc_drawing.rb +0 -5
  62. data/test/drawing/tc_graphic_frame.rb +4 -7
  63. data/test/drawing/tc_hyperlink.rb +0 -4
  64. data/test/drawing/tc_pic.rb +14 -3
  65. data/test/drawing/tc_two_cell_anchor.rb +3 -3
  66. data/test/profile.rb +7 -3
  67. data/test/rels/tc_relationship.rb +29 -11
  68. data/test/rels/tc_relationships.rb +12 -1
  69. data/test/stylesheet/tc_color.rb +6 -0
  70. data/test/stylesheet/tc_styles.rb +2 -2
  71. data/test/tc_helper.rb +1 -0
  72. data/test/tc_package.rb +30 -0
  73. data/test/util/tc_serialized_attributes.rb +19 -0
  74. data/test/workbook/tc_shared_strings_table.rb +6 -0
  75. data/test/workbook/tc_workbook.rb +23 -1
  76. data/test/workbook/worksheet/tc_break.rb +49 -0
  77. data/test/workbook/worksheet/tc_comment.rb +17 -6
  78. data/test/workbook/worksheet/tc_conditional_formatting.rb +2 -2
  79. data/test/workbook/worksheet/tc_date_time_converter.rb +3 -11
  80. data/test/workbook/worksheet/tc_pivot_table.rb +40 -22
  81. data/test/workbook/worksheet/tc_pivot_table_cache_definition.rb +9 -1
  82. data/test/workbook/worksheet/tc_sheet_view.rb +39 -39
  83. data/test/workbook/worksheet/tc_table.rb +2 -2
  84. data/test/workbook/worksheet/tc_worksheet.rb +39 -7
  85. data/test/workbook/worksheet/tc_worksheet_hyperlink.rb +2 -11
  86. metadata +37 -10
  87. data/test/example.xlsx +0 -0
@@ -136,7 +136,7 @@ module Axlsx
136
136
  def value_serialization(serialization_type, serialization_value, str = '')
137
137
  str << 't="' << serialization_type << '"' if serialization_type
138
138
  str << '><v>' << serialization_value << '</v>'
139
- end
139
+ end
140
140
 
141
141
 
142
142
  end
@@ -0,0 +1,35 @@
1
+ module Axlsx
2
+
3
+ # A collection of Brake objects.
4
+ # Please do not use this class directly. Instead use
5
+ # Worksheet#add_break
6
+ class ColBreaks < SimpleTypedList
7
+
8
+ # Instantiates a new list restricted to Break types
9
+ def initialize
10
+ super Break
11
+ end
12
+
13
+ # A column break specific helper for adding a break.
14
+ # @param [Hash] options A list of options to pass into the Break object
15
+ # The max and man options are fixed, however any other valid option for
16
+ # Break will be passed to the created break object.
17
+ # @see Break
18
+ def add_break(options)
19
+ @list << Break.new(options.merge(:max => 1048575, :man => true))
20
+ last
21
+ end
22
+
23
+ # Serialize the collection to xml
24
+ # @param [String] str The string to append this lists xml to.
25
+ # <colBreaks count="1" manualBreakCount="1">
26
+ # <brk id="3" max="1048575" man="1"/>
27
+ # </colBreaks>
28
+ def to_xml_string(str='')
29
+ return if empty?
30
+ str << '<colBreaks count="' << @list.size.to_s << '" manualBreakCount="' << @list.size.to_s << '">'
31
+ each { |brk| brk.to_xml_string(str) }
32
+ str << '</colBreaks>'
33
+ end
34
+ end
35
+ end
@@ -64,13 +64,14 @@ module Axlsx
64
64
  def to_xml_string(str = "")
65
65
  author = @comments.authors[author_index]
66
66
  str << '<comment ref="' << ref << '" authorId="' << author_index.to_s << '">'
67
- str << '<text><r>'
68
- str << '<rPr> <b/><color indexed="81"/></rPr>'
69
- str << '<t>' << author.to_s << ':
70
- </t></r>'
67
+ str << '<text>'
68
+ unless author.to_s == ""
69
+ str << '<r><rPr><b/><color indexed="81"/></rPr>'
70
+ str << "<t>" << ::CGI.escapeHTML(author.to_s) << ":\n</t></r>"
71
+ end
71
72
  str << '<r>'
72
73
  str << '<rPr><color indexed="81"/></rPr>'
73
- str << '<t>' << text << '</t></r></text>'
74
+ str << '<t>' << ::CGI.escapeHTML(text) << '</t></r></text>'
74
75
  str << '</comment>'
75
76
  end
76
77
 
@@ -56,9 +56,9 @@ module Axlsx
56
56
  # The relationships required by this object
57
57
  # @return [Array]
58
58
  def relationships
59
- [Relationship.new(VML_DRAWING_R, "../#{vml_drawing.pn}"),
60
- Relationship.new(COMMENT_R, "../#{pn}"),
61
- Relationship.new(COMMENT_R_NULL, "NULL")]
59
+ [Relationship.new(self, VML_DRAWING_R, "../#{vml_drawing.pn}"),
60
+ Relationship.new(self, COMMENT_R, "../#{pn}"),
61
+ Relationship.new(self, COMMENT_R_NULL, "NULL")]
62
62
  end
63
63
 
64
64
  # serialize the object
@@ -182,7 +182,7 @@ module Axlsx
182
182
  # @see timePeriod
183
183
  def timePeriod=(v); Axlsx::validate_time_period_type(v); @timePeriod = v end
184
184
  # @see formula
185
- def formula=(v); [*v].each {|x| Axlsx::validate_string(x) }; @formula = v end
185
+ def formula=(v); [*v].each {|x| Axlsx::validate_string(x) }; @formula = [*v].map { |form| ::CGI.escapeHTML(form) } end
186
186
 
187
187
  # @see color_scale
188
188
  def color_scale=(v)
@@ -10,7 +10,8 @@ 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
+ offset_date = date.respond_to?(:utc_offset) ? date + date.utc_offset.seconds : date
14
+ (offset_date - epoch).to_f
14
15
  end
15
16
 
16
17
  # The time_to_serial methond converts a Time object its excel serialized form.
@@ -19,11 +20,11 @@ module Axlsx
19
20
  def self.time_to_serial(time)
20
21
  # Using hardcoded offsets here as some operating systems will not except
21
22
  # a 'negative' offset from the ruby epoch.
22
- epoch1900 = -2209161600 # Time.utc(1899, 12, 30).to_i
23
- epoch1904 = -2082844800 # Time.utc(1904, 1, 1).to_i
24
- seconds_per_day = 86400 # 60*60*24
23
+ epoch1900 = -2209161600.0 # Time.utc(1899, 12, 30).to_i
24
+ epoch1904 = -2082844800.0 # Time.utc(1904, 1, 1).to_i
25
+ seconds_per_day = 86400.0 # 60*60*24
25
26
  epoch = Axlsx::Workbook::date1904 ? epoch1904 : epoch1900
26
- (time.to_f - epoch)/seconds_per_day
27
+ (time.utc_offset + time.to_f - epoch)/seconds_per_day
27
28
  end
28
29
  end
29
30
  end
@@ -19,10 +19,12 @@ module Axlsx
19
19
  @sheet = sheet
20
20
  @sheet.workbook.pivot_tables << self
21
21
  @name = "PivotTable#{index+1}"
22
+ @data_sheet = nil
22
23
  @rows = []
23
24
  @columns = []
24
25
  @data = []
25
26
  @pages = []
27
+ @subtotal = nil
26
28
  parse_options options
27
29
  yield self if block_given?
28
30
  end
@@ -39,6 +41,15 @@ module Axlsx
39
41
  # @return [String]
40
42
  attr_reader :sheet
41
43
 
44
+ # The sheet used as data source for the pivot table
45
+ # @return [Worksheet]
46
+ attr_writer :data_sheet
47
+
48
+ # @see #data_sheet
49
+ def data_sheet
50
+ @data_sheet || @sheet
51
+ end
52
+
42
53
  # The range where the data for this pivot table lives.
43
54
  # @return [String]
44
55
  attr_reader :range
@@ -85,10 +96,17 @@ module Axlsx
85
96
  # (see #data)
86
97
  def data=(v)
87
98
  DataTypeValidator.validate "#{self.class}.data", [Array], v
88
- v.each do |ref|
89
- DataTypeValidator.validate "#{self.class}.data[]", [String], ref
99
+ @data = []
100
+ v.each do |data_field|
101
+ if data_field.is_a? String
102
+ data_field = {:ref => data_field}
103
+ end
104
+ data_field.values.each do |value|
105
+ DataTypeValidator.validate "#{self.class}.data[]", [String], value
106
+ end
107
+ @data << data_field
90
108
  end
91
- @data = v
109
+ @data
92
110
  end
93
111
 
94
112
  # The pages
@@ -128,20 +146,14 @@ module Axlsx
128
146
  @cache_definition ||= PivotTableCacheDefinition.new(self)
129
147
  end
130
148
 
131
- # The worksheet relationships. This is managed automatically by the worksheet
149
+ # The relationships for this pivot table.
132
150
  # @return [Relationships]
133
151
  def relationships
134
152
  r = Relationships.new
135
- r << Relationship.new(PIVOT_TABLE_CACHE_DEFINITION_R, "../#{cache_definition.pn}")
153
+ r << Relationship.new(cache_definition, PIVOT_TABLE_CACHE_DEFINITION_R, "../#{cache_definition.pn}")
136
154
  r
137
155
  end
138
156
 
139
- # The relation reference id for this table
140
- # @return [String]
141
- def rId
142
- "rId#{index+1}"
143
- end
144
-
145
157
  # Serializes the object
146
158
  # @param [String] str
147
159
  # @return [String]
@@ -186,11 +198,11 @@ module Axlsx
186
198
  str << '</pageFields>'
187
199
  end
188
200
  unless data.empty?
189
- str << '<dataFields count="' << data.size.to_s << '">'
201
+ str << "<dataFields count=\"#{data.size}\">"
190
202
  data.each do |datum_value|
191
- str << '<dataField name="Sum of ' << datum_value << '" ' <<
192
- 'fld="' << header_index_of(datum_value).to_s << '" ' <<
193
- 'baseField="0" baseItem="0"/>'
203
+ str << "<dataField name='#{@subtotal} of #{datum_value[:ref]}' fld='#{header_index_of(datum_value[:ref])}' baseField='0' baseItem='0'"
204
+ str << " subtotal='#{datum_value[:subtotal]}' " if datum_value[:subtotal]
205
+ str << "/>"
194
206
  end
195
207
  str << '</dataFields>'
196
208
  end
@@ -206,7 +218,7 @@ module Axlsx
206
218
  # The header cells for the pivot table
207
219
  # @return [Array]
208
220
  def header_cells
209
- @sheet[header_range]
221
+ data_sheet[header_range]
210
222
  end
211
223
 
212
224
  # The values in the header cells collection
@@ -242,7 +254,7 @@ module Axlsx
242
254
  '<pivotField axis="axisCol" compact="0" outline="0" subtotalTop="0" showAll="0" includeNewItemsInFilter="1">' <<
243
255
  '<items count="1"><item t="default"/></items>' <<
244
256
  '</pivotField>'
245
- elsif data.include? cell_ref
257
+ elsif data_refs.include? cell_ref
246
258
  '<pivotField dataField="1" compact="0" outline="0" subtotalTop="0" showAll="0" includeNewItemsInFilter="1">' <<
247
259
  '</pivotField>'
248
260
  else
@@ -250,7 +262,9 @@ module Axlsx
250
262
  '</pivotField>'
251
263
  end
252
264
  end
253
-
265
+ def data_refs
266
+ data.map { |hash| hash[:ref] }
267
+ end
254
268
  def header_range
255
269
  range.gsub(/^(\w+?)(\d+)\:(\w+?)\d+$/, '\1\2:\3\2')
256
270
  end
@@ -35,10 +35,11 @@ module Axlsx
35
35
  index + 1
36
36
  end
37
37
 
38
- # The relation reference id for this table
38
+ # The relationship id for this pivot table cache definition.
39
+ # @see Relationship#Id
39
40
  # @return [String]
40
41
  def rId
41
- "rId#{index + 1}"
42
+ pivot_table.relationships.for(self).Id
42
43
  end
43
44
 
44
45
  # Serializes the object
@@ -48,7 +49,7 @@ module Axlsx
48
49
  str << '<?xml version="1.0" encoding="UTF-8"?>'
49
50
  str << '<pivotCacheDefinition xmlns="' << XML_NS << '" xmlns:r="' << XML_NS_R << '" invalid="1" refreshOnLoad="1" recordCount="0">'
50
51
  str << '<cacheSource type="worksheet">'
51
- str << '<worksheetSource ref="' << pivot_table.range << '" sheet="Data Sheet"/>'
52
+ str << '<worksheetSource ref="' << pivot_table.range << '" sheet="' << pivot_table.data_sheet.name << '"/>'
52
53
  str << '</cacheSource>'
53
54
  str << '<cacheFields count="' << pivot_table.header_cells_count.to_s << '">'
54
55
  pivot_table.header_cells.each do |cell|
@@ -17,7 +17,7 @@ module Axlsx
17
17
  # returns the relationships required by this collection
18
18
  def relationships
19
19
  return [] if empty?
20
- map{ |pivot_table| Relationship.new(PIVOT_TABLE_R, "../#{pivot_table.pn}") }
20
+ map{ |pivot_table| Relationship.new(pivot_table, PIVOT_TABLE_R, "../#{pivot_table.pn}") }
21
21
  end
22
22
  end
23
23
 
@@ -97,7 +97,7 @@ module Axlsx
97
97
  str << '</row>'
98
98
  end
99
99
 
100
- # Adds a singel sell to the row based on the data provided and updates the worksheet's autofit data.
100
+ # Adds a single sell to the row based on the data provided and updates the worksheet's autofit data.
101
101
  # @return [Cell]
102
102
  def add_cell(value="", options={})
103
103
  c = Cell.new(self, value, options)
@@ -0,0 +1,33 @@
1
+ module Axlsx
2
+
3
+ # A collection of break objects that define row breaks (page breaks) for printing and preview
4
+
5
+ class RowBreaks < SimpleTypedList
6
+
7
+ def initialize
8
+ super Break
9
+ end
10
+
11
+ # Adds a row break
12
+ # @param [Hash] options The options for the break to be created.
13
+ # max and man values are fixed.
14
+ # @see Break
15
+ def add_break(options)
16
+ # force feed the excel default
17
+ @list << Break.new(options.merge(:max => 16383, :man => true))
18
+ last
19
+ end
20
+
21
+ # <rowBreaks count="3" manualBreakCount="3">
22
+ # <brk id="1" max="16383" man="1"/>
23
+ # <brk id="7" max="16383" man="1"/>
24
+ # <brk id="13" max="16383" man="1"/>
25
+ # </rowBreaks>
26
+ def to_xml_string(str='')
27
+ return if empty?
28
+ str << '<rowBreaks count="' << @list.size.to_s << '" manualBreakCount="' << @list.size.to_s << '">'
29
+ each { |brk| brk.to_xml_string(str) }
30
+ str << '</rowBreaks>'
31
+ end
32
+ end
33
+ end
@@ -47,10 +47,11 @@ module Axlsx
47
47
  "#{TABLE_PN % (index+1)}"
48
48
  end
49
49
 
50
- # The relation reference id for this table
50
+ # The relationship id for this table.
51
+ # @see Relationship#Id
51
52
  # @return [String]
52
53
  def rId
53
- "rId#{index+1}"
54
+ @sheet.relationships.for(self).Id
54
55
  end
55
56
 
56
57
  # The name of the Table.
@@ -17,7 +17,7 @@ module Axlsx
17
17
  # returns the relationships required by this collection
18
18
  def relationships
19
19
  return [] if empty?
20
- map{ |table| Relationship.new(TABLE_R, "../#{table.pn}") }
20
+ map{ |table| Relationship.new(table, TABLE_R, "../#{table.pn}") }
21
21
  end
22
22
 
23
23
  def to_xml_string(str = "")
@@ -37,6 +37,8 @@ module Axlsx
37
37
  @page_setup = PageSetup.new options[:page_setup] if options[:page_setup]
38
38
  @print_options = PrintOptions.new options[:print_options] if options[:print_options]
39
39
  @header_footer = HeaderFooter.new options[:header_footer] if options[:header_footer]
40
+ @row_breaks = RowBreaks.new
41
+ @col_breaks = ColBreaks.new
40
42
  end
41
43
 
42
44
  # The name of the worksheet
@@ -94,6 +96,22 @@ module Axlsx
94
96
  @pivot_tables ||= PivotTables.new self
95
97
  end
96
98
 
99
+ # A collection of column breaks added to this worksheet
100
+ # @note Please do not use this directly. Instead use
101
+ # add_page_break
102
+ # @see Worksheet#add_page_break
103
+ def col_breaks
104
+ @col_breaks ||= ColBreaks.new
105
+ end
106
+
107
+ # A collection of row breaks added to this worksheet
108
+ # @note Please do not use this directly. Instead use
109
+ # add_page_break
110
+ # @see Worksheet#add_page_break
111
+ def row_breaks
112
+ @row_breaks ||= RowBreaks.new
113
+ end
114
+
97
115
  # A typed collection of hyperlinks associated with this worksheet
98
116
  # @return [WorksheetHyperlinks]
99
117
  def hyperlinks
@@ -114,14 +132,19 @@ module Axlsx
114
132
  @rows ||= SimpleTypedList.new Row
115
133
  end
116
134
 
117
- # returns the sheet data as columnw
118
- def cols
119
- @rows.transpose
135
+ # returns the sheet data as columns
136
+ # If you pass a block, it will be evaluated whenever a row does not have a
137
+ # cell at a specific index. The block will be called with the row and column
138
+ # index in the missing cell was found.
139
+ # @example
140
+ # cols { |row_index, column_index| p "warn - row #{row_index} is does not have a cell at #{column_index}
141
+ def cols(&block)
142
+ @rows.transpose(&block)
120
143
  end
121
144
 
122
- # An range that excel will apply an autfilter to "A1:B3"
145
+ # An range that excel will apply an auto-filter to "A1:B3"
123
146
  # This will turn filtering on for the cells in the range.
124
- # The first row is considered the header, while subsequent rows are considerd to be data.
147
+ # The first row is considered the header, while subsequent rows are considered to be data.
125
148
  # @return String
126
149
  def auto_filter
127
150
  @auto_filter ||= AutoFilter.new self
@@ -327,6 +350,10 @@ module Axlsx
327
350
  auto_filter.range = v
328
351
  end
329
352
 
353
+ # Accessor for controlling whether leading and trailing spaces in cells are
354
+ # preserved or ignored. The default is to preserve spaces.
355
+ attr_accessor :preserve_spaces
356
+
330
357
  # The part name of this worksheet
331
358
  # @return [String]
332
359
  def pn
@@ -339,10 +366,11 @@ module Axlsx
339
366
  "#{WORKSHEET_RELS_PN % (index+1)}"
340
367
  end
341
368
 
342
- # The relationship Id of thiw worksheet
369
+ # The relationship id of this worksheet.
343
370
  # @return [String]
371
+ # @see Relationship#Id
344
372
  def rId
345
- "rId#{index+1}"
373
+ @workbook.relationships.for(self).Id
346
374
  end
347
375
 
348
376
  # The index of this worksheet in the owning Workbook's worksheets list.
@@ -488,6 +516,24 @@ module Axlsx
488
516
  image
489
517
  end
490
518
 
519
+ # Adds a page break (row break) to the worksheet
520
+ # @param cell A Cell object or excel style string reference indicating where the break
521
+ # should be added to the sheet.
522
+ # @example
523
+ # ws.add_page_break("A4")
524
+ def add_page_break(cell)
525
+ DataTypeValidator.validate "Worksheet#add_page_break cell", [String, Cell], cell
526
+ column_index, row_index = if cell.is_a?(String)
527
+ Axlsx.name_to_indices(cell)
528
+ else
529
+ cell.pos
530
+ end
531
+ if column_index > 0
532
+ col_breaks.add_break(:id => column_index)
533
+ end
534
+ row_breaks.add_break(:id => row_index)
535
+ end
536
+
491
537
  # This is a helper method that Lets you specify a fixed width for multiple columns in a worksheet in one go.
492
538
  # Axlsx is sparse, so if you have not set data for a column, you cannot set the width.
493
539
  # Setting a fixed column width to nil will revert the behaviour back to calculating the width for you on the next call to add_row.
@@ -542,15 +588,7 @@ module Axlsx
542
588
  item.to_xml_string(str) if item
543
589
  end
544
590
  str << '</worksheet>'
545
- sanitize(str)
546
- end
547
-
548
- # returns the provided string with all invalid control charaters
549
- # removed.
550
- # @param [String] str The sting to process
551
- # @return [String]
552
- def sanitize(str)
553
- str.gsub(CONTROL_CHAR_REGEX, '')
591
+ Axlsx::sanitize(str)
554
592
  end
555
593
 
556
594
  # The worksheet relationships. This is managed automatically by the worksheet
@@ -565,14 +603,6 @@ module Axlsx
565
603
  r
566
604
  end
567
605
 
568
- # identifies the index of an object withing the collections used in generating relationships for the worksheet
569
- # @param [Any] object the object to search for
570
- # @return [Integer] The index of the object
571
- def relationships_index_of(object)
572
- objects = [tables.to_a, worksheet_comments.comments.to_a, hyperlinks.to_a, worksheet_drawing.drawing].flatten.compact || []
573
- objects.index(object)
574
- end
575
-
576
606
  # Returns the cell or cells defined using excel style A1:B3 references.
577
607
  # @param [String|Integer] cell_def the string defining the cell or range of cells, or the rownumber
578
608
  # @return [Cell, Array]
@@ -629,6 +659,11 @@ module Axlsx
629
659
  end
630
660
 
631
661
  private
662
+
663
+ def xml_space
664
+ workbook.xml_space
665
+ end
666
+
632
667
  def outline(collection, range, level = 1, collapsed = true)
633
668
  range.each do |index|
634
669
  unless (item = collection[index]).nil?
@@ -653,7 +688,7 @@ module Axlsx
653
688
  sheet_data, sheet_calc_pr, @sheet_protection, protected_ranges,
654
689
  auto_filter, merged_cells, conditional_formattings,
655
690
  data_validations, hyperlinks, print_options, page_margins,
656
- page_setup, header_footer, worksheet_drawing, worksheet_comments,
691
+ page_setup, header_footer, row_breaks, col_breaks, worksheet_drawing, worksheet_comments,
657
692
  tables]
658
693
  end
659
694
 
@@ -699,7 +734,7 @@ module Axlsx
699
734
  # Helper method for parsingout the root node for worksheet
700
735
  # @return [String]
701
736
  def worksheet_node
702
- "<worksheet xmlns=\"%s\" xmlns:r=\"%s\">" % [XML_NS, XML_NS_R]
737
+ "<worksheet xmlns=\"%s\" xmlns:r=\"%s\" xml:space=\"#{xml_space}\">" % [XML_NS, XML_NS_R]
703
738
  end
704
739
 
705
740
  def sheet_data