axlsx 1.3.6 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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