axlsx 1.3.6 → 2.0.1

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 +54 -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/doc_props/core.rb +6 -1
  12. data/lib/axlsx/drawing/axes.rb +7 -3
  13. data/lib/axlsx/drawing/bar_3D_chart.rb +2 -2
  14. data/lib/axlsx/drawing/chart.rb +20 -4
  15. data/lib/axlsx/drawing/drawing.rb +2 -17
  16. data/lib/axlsx/drawing/graphic_frame.rb +3 -8
  17. data/lib/axlsx/drawing/hyperlink.rb +5 -12
  18. data/lib/axlsx/drawing/marker.rb +25 -5
  19. data/lib/axlsx/drawing/one_cell_anchor.rb +9 -0
  20. data/lib/axlsx/drawing/pic.rb +17 -23
  21. data/lib/axlsx/drawing/two_cell_anchor.rb +7 -27
  22. data/lib/axlsx/package.rb +36 -16
  23. data/lib/axlsx/rels/relationship.rb +73 -8
  24. data/lib/axlsx/rels/relationships.rb +8 -1
  25. data/lib/axlsx/stylesheet/color.rb +1 -1
  26. data/lib/axlsx/stylesheet/num_fmt.rb +2 -2
  27. data/lib/axlsx/stylesheet/styles.rb +5 -3
  28. data/lib/axlsx/util/serialized_attributes.rb +11 -8
  29. data/lib/axlsx/util/simple_typed_list.rb +34 -13
  30. data/lib/axlsx/util/validators.rb +7 -0
  31. data/lib/axlsx/version.rb +1 -1
  32. data/lib/axlsx/workbook/defined_name.rb +1 -1
  33. data/lib/axlsx/workbook/shared_strings_table.rb +12 -3
  34. data/lib/axlsx/workbook/workbook.rb +31 -8
  35. data/lib/axlsx/workbook/worksheet/break.rb +37 -0
  36. data/lib/axlsx/workbook/worksheet/cell.rb +5 -5
  37. data/lib/axlsx/workbook/worksheet/cell_serializer.rb +1 -1
  38. data/lib/axlsx/workbook/worksheet/col_breaks.rb +35 -0
  39. data/lib/axlsx/workbook/worksheet/comment.rb +6 -5
  40. data/lib/axlsx/workbook/worksheet/comments.rb +3 -3
  41. data/lib/axlsx/workbook/worksheet/conditional_formatting_rule.rb +1 -1
  42. data/lib/axlsx/workbook/worksheet/date_time_converter.rb +6 -5
  43. data/lib/axlsx/workbook/worksheet/pivot_table.rb +32 -18
  44. data/lib/axlsx/workbook/worksheet/pivot_table_cache_definition.rb +4 -3
  45. data/lib/axlsx/workbook/worksheet/pivot_tables.rb +1 -1
  46. data/lib/axlsx/workbook/worksheet/row.rb +1 -1
  47. data/lib/axlsx/workbook/worksheet/row_breaks.rb +33 -0
  48. data/lib/axlsx/workbook/worksheet/table.rb +3 -2
  49. data/lib/axlsx/workbook/worksheet/tables.rb +1 -1
  50. data/lib/axlsx/workbook/worksheet/worksheet.rb +61 -26
  51. data/lib/axlsx/workbook/worksheet/worksheet_comments.rb +6 -5
  52. data/lib/axlsx/workbook/worksheet/worksheet_drawing.rb +3 -9
  53. data/lib/axlsx/workbook/worksheet/worksheet_hyperlink.rb +5 -10
  54. data/lib/axlsx.rb +10 -2
  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 +31 -1
  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 +39 -11
  87. data/test/example.xlsx +0 -0
@@ -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
data/lib/axlsx/package.rb CHANGED
@@ -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.
@@ -68,7 +70,7 @@ module Axlsx
68
70
 
69
71
  #def self.parse(input, confirm_valid = false)
70
72
  # p = Package.new
71
- # z = Zip::ZipFile.open(input)
73
+ # z = Zip::File.open(input)
72
74
  # p.workbook = Workbook.parse z.get_entry(WORKBOOK_PN)
73
75
  # p
74
76
  #end
@@ -98,7 +100,8 @@ 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?
101
- Zip::ZipOutputStream.open(output) do |zip|
103
+ Relationship.clear_cached_instances
104
+ Zip::OutputStream.open(output) do |zip|
102
105
  write_parts(zip)
103
106
  end
104
107
  true
@@ -110,7 +113,8 @@ 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?
113
- zip = write_parts(Zip::ZipOutputStream.new("streamed", true))
116
+ Relationship.clear_cached_instances
117
+ zip = write_parts(Zip::OutputStream.new("streamed", true))
114
118
  stream = zip.close_buffer
115
119
  stream.rewind
116
120
  stream
@@ -150,18 +154,18 @@ module Axlsx
150
154
  private
151
155
 
152
156
  # Writes the package parts to a zip archive.
153
- # @param [Zip::ZipOutputStream] zip
154
- # @return [Zip::ZipOutputStream]
157
+ # @param [Zip::OutputStream] zip
158
+ # @return [Zip::OutputStream]
155
159
  def write_parts(zip)
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 Entry 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::Entry]
187
+ def zip_entry_for_part(part)
188
+ timestamp = Zip::DOSTime.at(@core.created.to_i)
189
+ Zip::Entry.new("", part[:entry], "", "", 0, 0, Zip::Entry::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
@@ -3,7 +3,43 @@ module Axlsx
3
3
  # A relationship defines a reference between package parts.
4
4
  # @note Packages automatically manage relationships.
5
5
  class Relationship
6
+
7
+ class << self
8
+ # Keeps track of all instances of this class.
9
+ # @return [Array]
10
+ def instances
11
+ @instances ||= []
12
+ end
13
+
14
+ # Clear cached instances.
15
+ #
16
+ # This should be called before serializing a package (see {Package#serialize} and
17
+ # {Package#to_stream}) to make sure that serialization is idempotent (i.e.
18
+ # Relationship instances are generated with the same IDs everytime the package
19
+ # is serialized).
20
+ #
21
+ # Also, calling this avoids memory leaks (cached instances lingering around
22
+ # forever).
23
+ def clear_cached_instances
24
+ @instances = []
25
+ end
26
+
27
+ # Generate and return a unique id (eg. `rId123`) Used for setting {#Id}.
28
+ #
29
+ # The generated id depends on the number of cached instances, so using
30
+ # {clear_cached_instances} will automatically reset the generated ids, too.
31
+ # @return [String]
32
+ def next_free_id
33
+ "rId#{@instances.size + 1}"
34
+ end
35
+ end
6
36
 
37
+ # The id of the relationship (eg. "rId123"). Most instances get their own unique id.
38
+ # However, some instances need to share the same id – see {#should_use_same_id_as?}
39
+ # for details.
40
+ # @return [String]
41
+ attr_reader :Id
42
+
7
43
  # The location of the relationship target
8
44
  # @return [String]
9
45
  attr_reader :Target
@@ -30,14 +66,26 @@ module Axlsx
30
66
  # Target mode must be :external for now.
31
67
  attr_reader :TargetMode
32
68
 
33
- # creates a new relationship
69
+ # The source object the relations belongs to (e.g. a hyperlink, drawing, ...). Needed when
70
+ # looking up the relationship for a specific object (see {Relationships#for}).
71
+ attr_reader :source_obj
72
+
73
+ # Initializes a new relationship.
74
+ # @param [Object] source_obj see {#source_obj}
34
75
  # @param [String] type The type of the relationship
35
76
  # @param [String] target The target for the relationship
36
77
  # @option [Symbol] :target_mode only accepts :external.
37
- def initialize(type, target, options={})
78
+ def initialize(source_obj, type, target, options={})
79
+ @source_obj = source_obj
38
80
  self.Target=target
39
81
  self.Type=type
40
- self.TargetMode = options.delete(:target_mode) if options[:target_mode]
82
+ self.TargetMode = options[:target_mode] if options[:target_mode]
83
+ @Id = if (existing = self.class.instances.find{ |i| should_use_same_id_as?(i) })
84
+ existing.Id
85
+ else
86
+ self.class.next_free_id
87
+ end
88
+ self.class.instances << self
41
89
  end
42
90
 
43
91
  # @see Target
@@ -50,15 +98,32 @@ module Axlsx
50
98
 
51
99
  # serialize relationship
52
100
  # @param [String] str
53
- # @param [Integer] rId the id for this relationship
54
101
  # @return [String]
55
- def to_xml_string(rId, str = '')
56
- h = self.instance_values
57
- h[:Id] = 'rId' << rId.to_s
102
+ def to_xml_string(str = '')
103
+ h = self.instance_values.reject{|k, _| k == "source_obj"}
58
104
  str << '<Relationship '
59
105
  str << h.map { |key, value| '' << key.to_s << '="' << Axlsx::coder.encode(value.to_s) << '"'}.join(' ')
60
106
  str << '/>'
61
107
  end
62
-
108
+
109
+ # Whether this relationship should use the same id as `other`.
110
+ #
111
+ # Instances designating the same relationship need to use the same id. We can not simply
112
+ # compare the {#Target} attribute, though: `foo/bar.xml`, `../foo/bar.xml`,
113
+ # `../../foo/bar.xml` etc. are all different but probably mean the same file (this
114
+ # is especially an issue for relationships in the context of pivot tables). So lets
115
+ # just ignore this attribute for now (except when {#TargetMode} is set to `:External` –
116
+ # then {#Target} will be an absolute URL and thus can safely be compared).
117
+ #
118
+ # @todo Implement comparison of {#Target} based on normalized path names.
119
+ # @param other [Relationship]
120
+ def should_use_same_id_as?(other)
121
+ result = self.source_obj == other.source_obj && self.Type == other.Type && self.TargetMode == other.TargetMode
122
+ if self.TargetMode == :External
123
+ result &&= self.Target == other.Target
124
+ end
125
+ result
126
+ end
127
+
63
128
  end
64
129
  end
@@ -11,10 +11,17 @@ require 'axlsx/rels/relationship.rb'
11
11
  super Relationship
12
12
  end
13
13
 
14
+ # The relationship instance for the given source object, or nil if none exists.
15
+ # @see Relationship#source_obj
16
+ # @return [Relationship]
17
+ def for(source_obj)
18
+ @list.find{ |rel| rel.source_obj == source_obj }
19
+ end
20
+
14
21
  def to_xml_string(str = '')
15
22
  str << '<?xml version="1.0" encoding="UTF-8"?>'
16
23
  str << '<Relationships xmlns="' << RELS_R << '">'
17
- each_with_index { |rel, index| rel.to_xml_string(index+1, str) }
24
+ each{ |rel| rel.to_xml_string(str) }
18
25
  str << '</Relationships>'
19
26
  end
20
27
 
@@ -51,7 +51,7 @@ module Axlsx
51
51
  # @see color
52
52
  def rgb=(v)
53
53
  Axlsx::validate_string(v)
54
- v.upcase!
54
+ v = v.upcase
55
55
  v = v * 3 if v.size == 2
56
56
  v = v.rjust(8, 'FF')
57
57
  raise ArgumentError, "Invalid color rgb value: #{v}." unless v.match(/[0-9A-F]{8}/)
@@ -10,7 +10,7 @@ module Axlsx
10
10
  # Creates a new NumFmt object
11
11
  # @param [Hash] options Options for the number format object
12
12
  # @option [Integer] numFmtId The predefined format id or new format id for this format
13
- # @option [String] fomratCode The format code for this number format
13
+ # @option [String] formatCode The format code for this number format
14
14
  def initialize(options={})
15
15
  @numFmtId = 0
16
16
  @formatCode = ""
@@ -23,7 +23,7 @@ module Axlsx
23
23
  # @see http://support.microsoft.com/kb/264372
24
24
  attr_reader :formatCode
25
25
 
26
- # @return [Integer] An unsinged integer referencing a standard or custom number format.
26
+ # @return [Integer] An unsigned integer referencing a standard or custom number format.
27
27
  # @note
28
28
  # These are the known formats I can dig up. The constant NUM_FMT_PERCENT is 9, and uses the default % formatting. Axlsx also defines a few formats for date and time that are commonly used in asia as NUM_FMT_YYYYMMDD and NUM_FRM_YYYYMMDDHHMMSS.
29
29
  # 1 0
@@ -183,7 +183,7 @@ module Axlsx
183
183
  # :border=>Axlsx::STYLE_THIN_BORDER)
184
184
  #
185
185
  # # build your rows
186
- # ws.add_row ["Genreated At:", Time.now], :styles=>[nil, date_time]
186
+ # ws.add_row ["Generated At:", Time.now], :styles=>[nil, date_time]
187
187
  # ws.add_row ["Previous Year Quarterly Profits (JPY)"], :style=>title
188
188
  # ws.add_row ["Quarter", "Profit", "% of Total"], :style=>title
189
189
  # ws.add_row ["Q1", 4000, 40], :style=>[title, currency, percent]
@@ -296,9 +296,11 @@ module Axlsx
296
296
  def parse_fill_options(options={})
297
297
  return unless options[:bg_color]
298
298
  color = Color.new(:rgb=>options[:bg_color])
299
- pattern = PatternFill.new(:patternType =>:solid, :fgColor=>color)
299
+ dxf = options[:type] == :dxf
300
+ color_key = dxf ? :bgColor : :fgColor
301
+ pattern = PatternFill.new(:patternType =>:solid, color_key=>color)
300
302
  fill = Fill.new(pattern)
301
- options[:type] == :dxf ? fill : fills << fill
303
+ dxf ? fill : fills << fill
302
304
  end
303
305
 
304
306
  # parses Style#add_style options for borders.
@@ -40,18 +40,21 @@ module Axlsx
40
40
  # @param [Hash] additional_attributes An option key value hash for
41
41
  # defining values that are not serializable attributes list.
42
42
  def serialized_attributes(str = '', additional_attributes = {})
43
- key_value_pairs = instance_values
44
- key_value_pairs.each do |key, value|
45
- key_value_pairs.delete(key) if value == nil
46
- key_value_pairs.delete(key) unless self.class.xml_attributes.include?(key.to_sym)
47
- end
48
- key_value_pairs.merge! additional_attributes
49
- key_value_pairs.each do |key, value|
50
- str << "#{Axlsx.camel(key, false)}=\"#{value}\" "
43
+ attributes = declared_attributes.merge! additional_attributes
44
+ attributes.each do |key, value|
45
+ str << "#{Axlsx.camel(key, false)}=\"#{Axlsx.camel(value, false)}\" "
51
46
  end
52
47
  str
53
48
  end
54
49
 
50
+ # A hash of instance variables that have been declared with
51
+ # seraialized_attributes and are not nil.
52
+ # This requires ruby 1.9.3 or higher
53
+ def declared_attributes
54
+ instance_values.select do |key, value|
55
+ value != nil && self.class.xml_attributes.include?(key.to_sym)
56
+ end
57
+ end
55
58
 
56
59
  # serialized instance values at text nodes on a camelized element of the
57
60
  # attribute name. You may pass in a block for evaluation against non nil
@@ -1,21 +1,9 @@
1
1
  # encoding: UTF-8
2
2
  module Axlsx
3
+
3
4
  # A SimpleTypedList is a type restrictive collection that allows some of the methods from Array and supports basic xml serialization.
4
5
  # @private
5
6
  class SimpleTypedList
6
- # The class constants of allowed types
7
- # @return [Array]
8
- attr_reader :allowed_types
9
-
10
- # The index below which items cannot be removed
11
- # @return [Integer]
12
- attr_reader :locked_at
13
-
14
- # The tag name to use when serializing this object
15
- # by default the parent node for all items in the list is the classname of the first allowed type with the first letter in lowercase.
16
- # @return [String]
17
- attr_reader :serialize_as
18
-
19
7
  # Creats a new typed list
20
8
  # @param [Array, Class] type An array of Class objects or a single Class object
21
9
  # @param [String] serialize_as The tag name to use in serialization
@@ -33,6 +21,39 @@ module Axlsx
33
21
  @serialize_as = serialize_as
34
22
  end
35
23
 
24
+ # The class constants of allowed types
25
+ # @return [Array]
26
+ attr_reader :allowed_types
27
+
28
+ # The index below which items cannot be removed
29
+ # @return [Integer]
30
+ attr_reader :locked_at
31
+
32
+ # The tag name to use when serializing this object
33
+ # by default the parent node for all items in the list is the classname of the first allowed type with the first letter in lowercase.
34
+ # @return [String]
35
+ attr_reader :serialize_as
36
+
37
+ # Transposes the list (without blowing up like ruby does)
38
+ # any non populated cell in the matrix will be a nil value
39
+ def transpose
40
+ return @list.clone if @list.size == 0
41
+ row_count = @list.size
42
+ max_column_count = @list.map{|row| row.cells.size}.max
43
+ result = Array.new(max_column_count) { Array.new(row_count) }
44
+ # yes, I know it is silly, but that warning is really annoying
45
+ row_count.times do |row_index|
46
+ max_column_count.times do |column_index|
47
+ datum = if @list[row_index].cells.size >= max_column_count
48
+ @list[row_index].cells[column_index]
49
+ elsif block_given?
50
+ yield(column_index, row_index)
51
+ end
52
+ result[column_index][row_index] = datum
53
+ end
54
+ end
55
+ result
56
+ end
36
57
  # Lock this list at the current size
37
58
  # @return [self]
38
59
  def lock
@@ -290,4 +290,11 @@ module Axlsx
290
290
  def self.validate_split_state_type(v)
291
291
  RestrictionValidator.validate :split_state_type, [:frozen, :frozen_split, :split], v
292
292
  end
293
+
294
+ # Requires that the value is a valid "display blanks as" type.
295
+ # valid types must be one of gap, span, zero
296
+ # @param [Any] v The value validated
297
+ def self.validate_display_blanks_as(v)
298
+ RestrictionValidator.validate :display_blanks_as, [:gap, :span, :zero], v
299
+ end
293
300
  end
data/lib/axlsx/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Axlsx
2
2
 
3
3
  # The current version
4
- VERSION = "1.3.6"
4
+ VERSION = "2.0.1"
5
5
  end
@@ -105,7 +105,7 @@ module Axlsx
105
105
  attr_reader :local_sheet_id
106
106
 
107
107
  # The local sheet index (0-based)
108
- # @param [Integer] value the unsinged integer index of the sheet this defined_name applies to.
108
+ # @param [Integer] value the unsigned integer index of the sheet this defined_name applies to.
109
109
  def local_sheet_id=(value)
110
110
  Axlsx::validate_unsigned_int(value)
111
111
  @local_sheet_id = value