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.
- checksums.yaml +4 -4
- data/.yardopts_guide +19 -0
- data/CHANGELOG.md +8 -0
- data/README.md +54 -79
- data/Rakefile +0 -5
- data/examples/2010_comments.rb +17 -0
- data/examples/anchor_swapping.rb +28 -0
- data/examples/example.rb +16 -1
- data/examples/pivot_table.rb +2 -0
- data/examples/underline.rb +13 -0
- data/lib/axlsx/doc_props/core.rb +6 -1
- data/lib/axlsx/drawing/axes.rb +7 -3
- data/lib/axlsx/drawing/bar_3D_chart.rb +2 -2
- data/lib/axlsx/drawing/chart.rb +20 -4
- data/lib/axlsx/drawing/drawing.rb +2 -17
- data/lib/axlsx/drawing/graphic_frame.rb +3 -8
- data/lib/axlsx/drawing/hyperlink.rb +5 -12
- data/lib/axlsx/drawing/marker.rb +25 -5
- data/lib/axlsx/drawing/one_cell_anchor.rb +9 -0
- data/lib/axlsx/drawing/pic.rb +17 -23
- data/lib/axlsx/drawing/two_cell_anchor.rb +7 -27
- data/lib/axlsx/package.rb +36 -16
- data/lib/axlsx/rels/relationship.rb +73 -8
- data/lib/axlsx/rels/relationships.rb +8 -1
- data/lib/axlsx/stylesheet/color.rb +1 -1
- data/lib/axlsx/stylesheet/num_fmt.rb +2 -2
- data/lib/axlsx/stylesheet/styles.rb +5 -3
- data/lib/axlsx/util/serialized_attributes.rb +11 -8
- data/lib/axlsx/util/simple_typed_list.rb +34 -13
- data/lib/axlsx/util/validators.rb +7 -0
- data/lib/axlsx/version.rb +1 -1
- data/lib/axlsx/workbook/defined_name.rb +1 -1
- data/lib/axlsx/workbook/shared_strings_table.rb +12 -3
- data/lib/axlsx/workbook/workbook.rb +31 -8
- data/lib/axlsx/workbook/worksheet/break.rb +37 -0
- data/lib/axlsx/workbook/worksheet/cell.rb +5 -5
- data/lib/axlsx/workbook/worksheet/cell_serializer.rb +1 -1
- data/lib/axlsx/workbook/worksheet/col_breaks.rb +35 -0
- data/lib/axlsx/workbook/worksheet/comment.rb +6 -5
- data/lib/axlsx/workbook/worksheet/comments.rb +3 -3
- data/lib/axlsx/workbook/worksheet/conditional_formatting_rule.rb +1 -1
- data/lib/axlsx/workbook/worksheet/date_time_converter.rb +6 -5
- data/lib/axlsx/workbook/worksheet/pivot_table.rb +32 -18
- data/lib/axlsx/workbook/worksheet/pivot_table_cache_definition.rb +4 -3
- data/lib/axlsx/workbook/worksheet/pivot_tables.rb +1 -1
- data/lib/axlsx/workbook/worksheet/row.rb +1 -1
- data/lib/axlsx/workbook/worksheet/row_breaks.rb +33 -0
- data/lib/axlsx/workbook/worksheet/table.rb +3 -2
- data/lib/axlsx/workbook/worksheet/tables.rb +1 -1
- data/lib/axlsx/workbook/worksheet/worksheet.rb +61 -26
- data/lib/axlsx/workbook/worksheet/worksheet_comments.rb +6 -5
- data/lib/axlsx/workbook/worksheet/worksheet_drawing.rb +3 -9
- data/lib/axlsx/workbook/worksheet/worksheet_hyperlink.rb +5 -10
- data/lib/axlsx.rb +10 -2
- data/lib/schema/sml.xsd +4 -0
- data/test/axlsx.qcachegrind +2226 -0
- data/test/doc_props/tc_core.rb +7 -0
- data/test/drawing/tc_axes.rb +8 -0
- data/test/drawing/tc_bar_3D_chart.rb +6 -0
- data/test/drawing/tc_chart.rb +13 -0
- data/test/drawing/tc_drawing.rb +0 -5
- data/test/drawing/tc_graphic_frame.rb +4 -7
- data/test/drawing/tc_hyperlink.rb +0 -4
- data/test/drawing/tc_pic.rb +14 -3
- data/test/drawing/tc_two_cell_anchor.rb +3 -3
- data/test/profile.rb +7 -3
- data/test/rels/tc_relationship.rb +29 -11
- data/test/rels/tc_relationships.rb +12 -1
- data/test/stylesheet/tc_color.rb +6 -0
- data/test/stylesheet/tc_styles.rb +2 -2
- data/test/tc_helper.rb +1 -0
- data/test/tc_package.rb +31 -1
- data/test/util/tc_serialized_attributes.rb +19 -0
- data/test/workbook/tc_shared_strings_table.rb +6 -0
- data/test/workbook/tc_workbook.rb +23 -1
- data/test/workbook/worksheet/tc_break.rb +49 -0
- data/test/workbook/worksheet/tc_comment.rb +17 -6
- data/test/workbook/worksheet/tc_conditional_formatting.rb +2 -2
- data/test/workbook/worksheet/tc_date_time_converter.rb +3 -11
- data/test/workbook/worksheet/tc_pivot_table.rb +40 -22
- data/test/workbook/worksheet/tc_pivot_table_cache_definition.rb +9 -1
- data/test/workbook/worksheet/tc_sheet_view.rb +39 -39
- data/test/workbook/worksheet/tc_table.rb +2 -2
- data/test/workbook/worksheet/tc_worksheet.rb +39 -7
- data/test/workbook/worksheet/tc_worksheet_hyperlink.rb +2 -11
- metadata +39 -11
- data/test/example.xlsx +0 -0
data/lib/axlsx/drawing/marker.rb
CHANGED
|
@@ -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
|
-
#
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
|
data/lib/axlsx/drawing/pic.rb
CHANGED
|
@@ -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
|
|
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
|
|
106
|
-
|
|
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.
|
|
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.
|
|
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="
|
|
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
|
-
|
|
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
|
-
|
|
199
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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::
|
|
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
|
-
|
|
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
|
-
|
|
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::
|
|
154
|
-
# @return [Zip::
|
|
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
|
|
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
|
|
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
|
-
#
|
|
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
|
|
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(
|
|
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
|
-
|
|
24
|
+
each{ |rel| rel.to_xml_string(str) }
|
|
18
25
|
str << '</Relationships>'
|
|
19
26
|
end
|
|
20
27
|
|
|
@@ -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]
|
|
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
|
|
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 ["
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
@@ -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
|
|
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
|