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
@@ -142,10 +142,10 @@ module Axlsx
142
142
  end
143
143
 
144
144
  # A hash of axes used by this chart. Bar charts have a value and
145
- # category axes specified via axex[:val_axes] and axes[:cat_axis]
145
+ # category axes specified via axes[:val_axes] and axes[:cat_axis]
146
146
  # @return [Axes]
147
147
  def axes
148
- @axes ||= Axes.new(:val_axis => ValAxis, :cat_axis => CatAxis)
148
+ @axes ||= Axes.new(:cat_axis => CatAxis, :val_axis => ValAxis)
149
149
  end
150
150
  end
151
151
  end
@@ -20,6 +20,7 @@ module Axlsx
20
20
  @graphic_frame.anchor.drawing.worksheet.workbook.charts << self
21
21
  @series = SimpleTypedList.new Series
22
22
  @show_legend = true
23
+ @display_blanks_as = :gap
23
24
  @series_type = Series
24
25
  @title = Title.new
25
26
  parse_options options
@@ -70,10 +71,19 @@ module Axlsx
70
71
  # @return [Boolean]
71
72
  attr_reader :show_legend
72
73
 
73
- # returns a relationship object for the chart
74
- # @return [Axlsx::Relationship]
74
+ # How to display blank values
75
+ # Options are
76
+ # * gap: Display nothing
77
+ # * span: Not sure what this does
78
+ # * zero: Display as if the value were zero, not blank
79
+ # @return [Symbol]
80
+ # Default :gap (although this really should vary by chart type and grouping)
81
+ attr_reader :display_blanks_as
82
+
83
+ # The relationship object for this chart.
84
+ # @return [Relationship]
75
85
  def relationship
76
- Relationship.new(CHART_R, "../#{pn}")
86
+ Relationship.new(self, CHART_R, "../#{pn}")
77
87
  end
78
88
 
79
89
  # The index of this chart in the workbooks charts collection
@@ -105,6 +115,12 @@ module Axlsx
105
115
  # @return [Boolean]
106
116
  def show_legend=(v) Axlsx::validate_boolean(v); @show_legend = v; end
107
117
 
118
+ # How to display blank values
119
+ # @see display_blanks_as
120
+ # @param [Symbol] v
121
+ # @return [Symbol]
122
+ def display_blanks_as=(v) Axlsx::validate_display_blanks_as(v); @display_blanks_as = v; end
123
+
108
124
  # The style for the chart.
109
125
  # see ECMA Part 1 §21.2.2.196
110
126
  # @param [Integer] v must be between 1 and 48
@@ -157,7 +173,7 @@ module Axlsx
157
173
  str << '</c:legend>'
158
174
  end
159
175
  str << '<c:plotVisOnly val="1"/>'
160
- str << '<c:dispBlanksAs val="zero"/>'
176
+ str << '<c:dispBlanksAs val="' << display_blanks_as.to_s << '"/>'
161
177
  str << '<c:showDLblsOverMax val="1"/>'
162
178
  str << '</c:chart>'
163
179
  str << '<c:printSettings>'
@@ -82,7 +82,7 @@ module Axlsx
82
82
  TwoCellAnchor.new(self, options).add_pic(options)
83
83
  else
84
84
  OneCellAnchor.new(self, options)
85
- end
85
+ end
86
86
  @anchors.last.object
87
87
  end
88
88
 
@@ -121,12 +121,6 @@ module Axlsx
121
121
  @worksheet.workbook.drawings.index(self)
122
122
  end
123
123
 
124
- # The relation reference id for this drawing
125
- # @return [String]
126
- def rId
127
- "rId#{index+1}"
128
- end
129
-
130
124
  # The part name for this drawing
131
125
  # @return [String]
132
126
  def pn
@@ -140,15 +134,7 @@ module Axlsx
140
134
  "#{DRAWING_RELS_PN % (index+1)}"
141
135
  end
142
136
 
143
- # The index of a chart, image or hyperlink object this drawing contains
144
- def index_of(object)
145
- child_objects.index(object)
146
- end
147
-
148
-
149
- # An ordered list of objects this drawing holds
150
- # It is important that the objects are returned in the same order each time for
151
- # releationship indexing in the package
137
+ # A list of objects this drawing holds.
152
138
  # @return [Array]
153
139
  def child_objects
154
140
  charts + images + hyperlinks
@@ -168,7 +154,6 @@ module Axlsx
168
154
  def to_xml_string(str = '')
169
155
  str << '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
170
156
  str << '<xdr:wsDr xmlns:xdr="' << XML_NS_XDR << '" xmlns:a="' << XML_NS_A << '">'
171
-
172
157
  anchors.each { |anchor| anchor.to_xml_string(str) }
173
158
  str << '</xdr:wsDr>'
174
159
  end
@@ -22,15 +22,10 @@ module Axlsx
22
22
  @chart = chart_type.new(self, options)
23
23
  end
24
24
 
25
- # The relationship id for this graphic
25
+ # The relationship id for this graphic frame.
26
26
  # @return [String]
27
- #
28
- # NOTE: Discontinued. This should not be part of GraphicFrame.
29
- # The drawing object maintains relationships and needs to be queried to determine the relationship id of any given graphic data child object.
30
- #
31
27
  def rId
32
- warn('axlsx::DEPRECIATED: GraphicFrame#rId has been depreciated. relationship id is determed by the drawing object')
33
- "rId#{@anchor.index+1}"
28
+ @anchor.drawing.relationships.for(chart).Id
34
29
  end
35
30
 
36
31
  # Serializes the object
@@ -49,7 +44,7 @@ module Axlsx
49
44
  str << '</xdr:xfrm>'
50
45
  str << '<a:graphic>'
51
46
  str << '<a:graphicData uri="' << XML_NS_C << '">'
52
- str << '<c:chart xmlns:c="' << XML_NS_C << '" xmlns:r="' << XML_NS_R << '" r:id="rId' << (@anchor.drawing.index_of(@chart)+1).to_s << '"/>'
47
+ str << '<c:chart xmlns:c="' << XML_NS_C << '" xmlns:r="' << XML_NS_R << '" r:id="' << rId << '"/>'
53
48
  str << '</a:graphicData>'
54
49
  str << '</a:graphic>'
55
50
  str << '</xdr:graphicFrame>'
@@ -83,27 +83,20 @@ module Axlsx
83
83
  # @return [String]
84
84
  attr_accessor :tooltip
85
85
 
86
- # Returns a relationship object for this hyperlink
87
- # @return [Axlsx::Relationship]
86
+ # The relationship object for this hyperlink.
87
+ # @return [Relationship]
88
88
  def relationship
89
- Relationship.new(HYPERLINK_R, href, :target_mode => :External)
89
+ Relationship.new(self, HYPERLINK_R, href, :target_mode => :External)
90
90
  end
91
+
91
92
  # Serializes the object
92
93
  # @param [String] str
93
94
  # @return [String]
94
95
  def to_xml_string(str = '')
95
96
  str << '<a:hlinkClick '
96
- serialized_attributes str, {:'r:id' => "rId#{id}", :'xmlns:r' => XML_NS_R }
97
+ serialized_attributes str, {:'r:id' => relationship.Id, :'xmlns:r' => XML_NS_R }
97
98
  str << '/>'
98
99
  end
99
100
 
100
- private
101
-
102
- # The relational ID for this hyperlink
103
- # @return [Integer]
104
- def id
105
- @parent.anchor.drawing.index_of(self)+1
106
- end
107
-
108
101
  end
109
102
  end
@@ -43,11 +43,14 @@ module Axlsx
43
43
  def rowOff=(v) Axlsx::validate_int v; @rowOff = v end
44
44
 
45
45
  # shortcut to set the column, row position for this marker
46
- # @param col the column for the marker
47
- # @param row the row of the marker
48
- def coord(col, row)
49
- self.col = col
50
- self.row = row
46
+ # @param col the column for the marker, a Cell object or a string reference like "B7"
47
+ # or an Array.
48
+ # @param row the row of the marker. This is ignored if the col parameter is a Cell or
49
+ # String or Array.
50
+ def coord(col, row=0)
51
+ coordinates = parse_coord_args(col, row)
52
+ self.col = coordinates[0]
53
+ self.row = coordinates[1]
51
54
  end
52
55
 
53
56
  # Serializes the object
@@ -58,6 +61,23 @@ module Axlsx
58
61
  str << '<xdr:' << k.to_s << '>' << self.send(k).to_s << '</xdr:' << k.to_s << '>'
59
62
  end
60
63
  end
64
+ private
65
+
66
+ # handles multiple inputs for setting the position of a marker
67
+ # @see Chart#start_at
68
+ def parse_coord_args(x, y=0)
69
+ if x.is_a?(String)
70
+ x, y = *Axlsx::name_to_indices(x)
71
+ end
72
+ if x.is_a?(Cell)
73
+ x, y = *x.pos
74
+ end
75
+ if x.is_a?(Array)
76
+ x, y = *x
77
+ end
78
+ [x, y]
79
+ end
80
+
61
81
 
62
82
  end
63
83
 
@@ -48,6 +48,15 @@ module Axlsx
48
48
  # @return [Integer]
49
49
  attr_reader :height
50
50
 
51
+ # sets the starting position for the anchor.
52
+ # You can provide a String like "A1", an array like [0,0] or a cell object for the x parameter.
53
+ # We just 'figure it out' for you.
54
+ # @param [Array, String, Cell, Integer] x Accepts many inputs for defining the starting position of the cell.
55
+ # @param [Integer] y When x is an integer, this value is used for the row index at which the anchor starts.
56
+ def start_at(x, y=0)
57
+ from.coord x, y
58
+ end
59
+ #
51
60
  # @see height
52
61
  def height=(v) Axlsx::validate_unsigned_int(v); @height = v; end
53
62
 
@@ -37,7 +37,7 @@ module Axlsx
37
37
  attr_reader :descr
38
38
 
39
39
  # The path to the image you want to include
40
- # Only local images are supported at this time and only jpg support
40
+ # Only local images are supported at this time.
41
41
  # @return [String]
42
42
  attr_reader :image_src
43
43
 
@@ -67,7 +67,7 @@ module Axlsx
67
67
 
68
68
  def image_src=(v)
69
69
  Axlsx::validate_string(v)
70
- RestrictionValidator.validate 'Pic.image_src', ALLOWED_EXTENSIONS, File.extname(v).delete('.')
70
+ RestrictionValidator.validate 'Pic.image_src', ALLOWED_EXTENSIONS, File.extname(v.downcase).delete('.')
71
71
  raise ArgumentError, "File does not exist" unless File.exist?(v)
72
72
  @image_src = v
73
73
  end
@@ -89,7 +89,8 @@ module Axlsx
89
89
  # @return [String]
90
90
  def extname
91
91
  File.extname(image_src).delete('.') unless image_src.nil?
92
- end
92
+ end
93
+
93
94
  # The index of this image in the workbooks images collections
94
95
  # @return [Index]
95
96
  def index
@@ -102,15 +103,10 @@ module Axlsx
102
103
  "#{IMAGE_PN % [(index+1), extname]}"
103
104
  end
104
105
 
105
- # The relational id withing the drawing's relationships
106
- def id
107
- @anchor.drawing.charts.size + @anchor.drawing.images.index(self) + 1
108
- end
109
-
110
- # Returns a relationship object for this object
111
- # @return Axlsx::Relationship
106
+ # The relationship object for this pic.
107
+ # @return [Relationship]
112
108
  def relationship
113
- Relationship.new(IMAGE_R, "../#{pn}")
109
+ Relationship.new(self, IMAGE_R, "../#{pn}")
114
110
  end
115
111
 
116
112
  # providing access to the anchor's width attribute
@@ -148,9 +144,8 @@ module Axlsx
148
144
  # @param [Integer] x The column
149
145
  # @param [Integer] y The row
150
146
  # @return [Marker]
151
- def start_at(x, y)
152
- @anchor.from.col = x
153
- @anchor.from.row = y
147
+ def start_at(x, y=nil)
148
+ @anchor.start_at x, y
154
149
  @anchor.from
155
150
  end
156
151
 
@@ -158,10 +153,9 @@ module Axlsx
158
153
  # @param [Integer] x The column
159
154
  # @param [Integer] y The row
160
155
  # @return [Marker]
161
- def end_at(x, y)
156
+ def end_at(x, y=nil)
162
157
  use_two_cell_anchor unless @anchor.is_a?(TwoCellAnchor)
163
- @anchor.to.col = x
164
- @anchor.to.row = y
158
+ @anchor.end_at x, y
165
159
  @anchor.to
166
160
  end
167
161
 
@@ -177,7 +171,7 @@ module Axlsx
177
171
  picture_locking.to_xml_string(str)
178
172
  str << '</xdr:cNvPicPr></xdr:nvPicPr>'
179
173
  str << '<xdr:blipFill>'
180
- str << '<a:blip xmlns:r ="' << XML_NS_R << '" r:embed="rId' << id.to_s << '"/>'
174
+ str << '<a:blip xmlns:r ="' << XML_NS_R << '" r:embed="' << relationship.Id << '"/>'
181
175
  str << '<a:stretch><a:fillRect/></a:stretch></xdr:blipFill><xdr:spPr>'
182
176
  str << '<a:xfrm><a:off x="0" y="0"/><a:ext cx="2336800" cy="2161540"/></a:xfrm>'
183
177
  str << '<a:prstGeom prst="rect"><a:avLst/></a:prstGeom></xdr:spPr></xdr:pic>'
@@ -189,22 +183,22 @@ module Axlsx
189
183
  # Changes the anchor to a one cell anchor.
190
184
  def use_one_cell_anchor
191
185
  return if @anchor.is_a?(OneCellAnchor)
192
- swap_anchor(OneCellAnchor.new(@anchor.drawing, :from => @anchor.from))
186
+ new_anchor = OneCellAnchor.new(@anchor.drawing, :start_at => [@anchor.from.col, @anchor.from.row])
187
+ swap_anchor(new_anchor)
193
188
  end
194
189
 
195
190
  #changes the anchor type to a two cell anchor
196
191
  def use_two_cell_anchor
197
192
  return if @anchor.is_a?(TwoCellAnchor)
198
- swap_anchor(TwoCellAnchor.new(@anchor.drawing)).tap do |new_anchor|
199
- new_anchor.from.col = @anchor.from.col
200
- new_anchor.from.row = @anchor.from.row
201
- end
193
+ new_anchor = TwoCellAnchor.new(@anchor.drawing, :start_at => [@anchor.from.col, @anchor.from.row])
194
+ swap_anchor(new_anchor)
202
195
  end
203
196
 
204
197
  # refactoring of swapping code, law of demeter be damned!
205
198
  def swap_anchor(new_anchor)
206
199
  new_anchor.drawing.anchors.delete(new_anchor)
207
200
  @anchor.drawing.anchors[@anchor.drawing.anchors.index(@anchor)] = new_anchor
201
+ new_anchor.instance_variable_set "@object", @anchor.object
208
202
  @anchor = new_anchor
209
203
  end
210
204
  end
@@ -5,6 +5,8 @@ module Axlsx
5
5
  # @see Worksheet#add_chart
6
6
  class TwoCellAnchor
7
7
 
8
+ include Axlsx::OptionsParser
9
+
8
10
  # A marker that defines the from cell anchor. The default from column and row are 0 and 0 respectively
9
11
  # @return [Marker]
10
12
  attr_reader :from
@@ -34,22 +36,23 @@ module Axlsx
34
36
  @drawing = drawing
35
37
  drawing.anchors << self
36
38
  @from, @to = Marker.new, Marker.new(:col => 5, :row=>10)
39
+ parse_options options
37
40
  end
38
41
 
39
42
  # sets the col, row attributes for the from marker.
40
43
  # @note The recommended way to set the start position for graphical
41
44
  # objects is directly thru the object.
42
45
  # @see Chart#start_at
43
- def start_at(x, y)
44
- set_marker_coords(x, y, from)
46
+ def start_at(x, y=nil)
47
+ from.coord x, y
45
48
  end
46
49
 
47
50
  # sets the col, row attributes for the to marker
48
51
  # @note the recommended way to set the to position for graphical
49
52
  # objects is directly thru the object
50
53
  # @see Char#end_at
51
- def end_at(x, y)
52
- set_marker_coords(x, y, to)
54
+ def end_at(x, y=nil)
55
+ to.coord x, y
53
56
  end
54
57
 
55
58
  # Creates a graphic frame and chart object associated with this anchor
@@ -85,28 +88,5 @@ module Axlsx
85
88
  str << '<xdr:clientData/>'
86
89
  str << '</xdr:twoCellAnchor>'
87
90
  end
88
- private
89
-
90
- # parses coordinates and sets up a marker's row/col propery
91
- def set_marker_coords(x, y, marker)
92
- marker.col, marker.row = *parse_coord_args(x, y)
93
- end
94
-
95
- # handles multiple inputs for setting the position of a marker
96
- # @see Chart#start_at
97
- def parse_coord_args(x, y=0)
98
- if x.is_a?(String)
99
- x, y = *Axlsx::name_to_indices(x)
100
- end
101
- if x.is_a?(Cell)
102
- x, y = *x.pos
103
- end
104
- if x.is_a?(Array)
105
- x, y = *x
106
- end
107
- [x, y]
108
- end
109
-
110
-
111
91
  end
112
92
  end
@@ -17,12 +17,14 @@ module Axlsx
17
17
  #
18
18
  # @param [Hash] options A hash that you can use to specify the author and workbook for this package.
19
19
  # @option options [String] :author The author of the document
20
+ # @option options [Time] :created_at Timestamp in the document properties (defaults to current time).
20
21
  # @option options [Boolean] :use_shared_strings This is passed to the workbook to specify that shared strings should be used when serializing the package.
21
22
  # @example Package.new :author => 'you!', :workbook => Workbook.new
22
23
  def initialize(options={})
23
24
  @workbook = nil
24
25
  @core, @app = Core.new, App.new
25
26
  @core.creator = options[:author] || @core.creator
27
+ @core.created = options[:created_at]
26
28
  parse_options options
27
29
  yield self if block_given?
28
30
  end
@@ -35,12 +37,6 @@ module Axlsx
35
37
  end
36
38
 
37
39
 
38
- # Shortcut to specify that the workbook should use shared strings
39
- # @see Workbook#use_shared_strings
40
- def use_shared_strings=(v)
41
- Axlsx::validate_boolean(v);
42
- workbook.use_shared_strings = v
43
- end
44
40
 
45
41
  # Shortcut to determine if the workbook is configured to use shared strings
46
42
  # @see Workbook#use_shared_strings
@@ -48,6 +44,12 @@ module Axlsx
48
44
  workbook.use_shared_strings
49
45
  end
50
46
 
47
+ # Shortcut to specify that the workbook should use shared strings
48
+ # @see Workbook#use_shared_strings
49
+ def use_shared_strings=(v)
50
+ Axlsx::validate_boolean(v);
51
+ workbook.use_shared_strings = v
52
+ end
51
53
  # The workbook this package will serialize or validate.
52
54
  # @return [Workbook] If no workbook instance has been assigned with this package a new Workbook instance is returned.
53
55
  # @raise ArgumentError if workbook parameter is not a Workbook instance.
@@ -98,6 +100,7 @@ module Axlsx
98
100
  # File.open('example_streamed.xlsx', 'w') { |f| f.write(s.read) }
99
101
  def serialize(output, confirm_valid=false)
100
102
  return false unless !confirm_valid || self.validate.empty?
103
+ Relationship.clear_cached_instances
101
104
  Zip::ZipOutputStream.open(output) do |zip|
102
105
  write_parts(zip)
103
106
  end
@@ -110,6 +113,7 @@ module Axlsx
110
113
  # @return [StringIO|Boolean] False if confirm_valid and validation errors exist. rewound string IO if not.
111
114
  def to_stream(confirm_valid=false)
112
115
  return false unless !confirm_valid || self.validate.empty?
116
+ Relationship.clear_cached_instances
113
117
  zip = write_parts(Zip::ZipOutputStream.new("streamed", true))
114
118
  stream = zip.close_buffer
115
119
  stream.rewind
@@ -156,12 +160,12 @@ module Axlsx
156
160
  p = parts
157
161
  p.each do |part|
158
162
  unless part[:doc].nil?
159
- zip.put_next_entry(part[:entry])
163
+ zip.put_next_entry(zip_entry_for_part(part))
160
164
  entry = ['1.9.2', '1.9.3'].include?(RUBY_VERSION) ? part[:doc].force_encoding('BINARY') : part[:doc]
161
165
  zip.puts(entry)
162
166
  end
163
167
  unless part[:path].nil?
164
- zip.put_next_entry(part[:entry]);
168
+ zip.put_next_entry(zip_entry_for_part(part))
165
169
  # binread for 1.9.3
166
170
  zip.write IO.respond_to?(:binread) ? IO.binread(part[:path]) : IO.read(part[:path])
167
171
  end
@@ -169,6 +173,22 @@ module Axlsx
169
173
  zip
170
174
  end
171
175
 
176
+ # Generate a ZipEntry for the given package part.
177
+ # The important part here is to explicitly set the timestamp for the zip entry: Serializing axlsx packages
178
+ # with identical contents should result in identical zip files – however, the timestamp of a zip entry
179
+ # defaults to the time of serialization and therefore the zip file contents would be different every time
180
+ # the package is serialized.
181
+ #
182
+ # Note: {Core#created} also defaults to the current time – so to generate identical axlsx packages you have
183
+ # to set this explicitly, too (eg. with `Package.new(created_at: Time.local(2013, 1, 1))`).
184
+ #
185
+ # @param part A hash describing a part of this pacakge (see {#parts})
186
+ # @return [Zip::ZipEntry]
187
+ def zip_entry_for_part(part)
188
+ timestamp = Zip::DOSTime.at(@core.created.to_i)
189
+ Zip::ZipEntry.new("", part[:entry], "", "", 0, 0, Zip::ZipEntry::DEFLATED, 0, timestamp)
190
+ end
191
+
172
192
  # The parts of a package
173
193
  # @return [Array] An array of hashes that define the entry, document and schema for each part of the package.
174
194
  # @private
@@ -321,9 +341,9 @@ module Axlsx
321
341
  # @private
322
342
  def relationships
323
343
  rels = Axlsx::Relationships.new
324
- rels << Relationship.new(WORKBOOK_R, WORKBOOK_PN)
325
- rels << Relationship.new(CORE_R, CORE_PN)
326
- rels << Relationship.new(APP_R, APP_PN)
344
+ rels << Relationship.new(self, WORKBOOK_R, WORKBOOK_PN)
345
+ rels << Relationship.new(self, CORE_R, CORE_PN)
346
+ rels << Relationship.new(self, APP_R, APP_PN)
327
347
  rels.lock
328
348
  rels
329
349
  end