write_xlsx 1.12.3 → 1.13.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +7 -0
- data/Changes +3 -0
- data/README.md +1 -1
- data/lib/write_xlsx/chartsheet.rb +4 -1
- data/lib/write_xlsx/object_positioning.rb +189 -0
- data/lib/write_xlsx/package/app.rb +1 -1
- data/lib/write_xlsx/page_setup.rb +190 -0
- data/lib/write_xlsx/sheets.rb +3 -3
- data/lib/write_xlsx/utility.rb +57 -9
- data/lib/write_xlsx/version.rb +1 -1
- data/lib/write_xlsx/workbook.rb +34 -34
- data/lib/write_xlsx/worksheet/asset_manager.rb +60 -0
- data/lib/write_xlsx/worksheet/autofilter.rb +388 -0
- data/lib/write_xlsx/worksheet/cell_data.rb +8 -5
- data/lib/write_xlsx/worksheet/cell_data_manager.rb +47 -0
- data/lib/write_xlsx/worksheet/cell_data_store.rb +61 -0
- data/lib/write_xlsx/worksheet/columns.rb +199 -0
- data/lib/write_xlsx/worksheet/comments_support.rb +61 -0
- data/lib/write_xlsx/worksheet/conditional_formats.rb +30 -0
- data/lib/write_xlsx/worksheet/data_writing.rb +990 -0
- data/lib/write_xlsx/worksheet/drawing_methods.rb +308 -0
- data/lib/write_xlsx/worksheet/drawing_preparation.rb +290 -0
- data/lib/write_xlsx/worksheet/drawing_relations.rb +76 -0
- data/lib/write_xlsx/worksheet/drawing_xml_writer.rb +50 -0
- data/lib/write_xlsx/worksheet/formatting.rb +416 -0
- data/lib/write_xlsx/worksheet/initialization.rb +146 -0
- data/lib/write_xlsx/worksheet/panes.rb +64 -0
- data/lib/write_xlsx/worksheet/print_options.rb +72 -0
- data/lib/write_xlsx/worksheet/protection.rb +65 -0
- data/lib/write_xlsx/worksheet/rich_text_helpers.rb +78 -0
- data/lib/write_xlsx/worksheet/row_col_sizing.rb +67 -0
- data/lib/write_xlsx/worksheet/rows.rb +84 -0
- data/lib/write_xlsx/worksheet/selection.rb +41 -0
- data/lib/write_xlsx/worksheet/xml_writer.rb +1241 -0
- data/lib/write_xlsx/worksheet.rb +359 -4530
- metadata +26 -3
- data/lib/write_xlsx/worksheet/page_setup.rb +0 -192
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8da4e683796759c1d6cdee00aaba72671bb7ea86e7ece2a794628d04289c880a
|
|
4
|
+
data.tar.gz: dd59c677e92a15eb273d6290c093e90c2a560d4c087a937c2ee343f2cf5286f4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 33646d4746630950a08a4f29546682a2c52c04db52df9e201c37b42d31e1033ecd486904009b09b88d637a0d7074f65ac92025c9ea0755e427f3d7d7183da33c
|
|
7
|
+
data.tar.gz: a6f60317b950822df36b6cc3eacfd4922bcff4026f090714964d547eaa8227b9e5cb56c6f49ea33686601bf95d9c6788b8b9d70dc1d8f46f46ce1e16855420bb
|
data/.rubocop.yml
CHANGED
|
@@ -46,6 +46,10 @@ Lint/DuplicateBranch:
|
|
|
46
46
|
IgnoreLiteralBranches: true
|
|
47
47
|
Exclude:
|
|
48
48
|
- 'lib/write_xlsx/worksheet.rb'
|
|
49
|
+
- 'lib/write_xlsx/worksheet/data_writing.rb'
|
|
50
|
+
|
|
51
|
+
Lint/FloatComparison:
|
|
52
|
+
Enabled: false
|
|
49
53
|
|
|
50
54
|
Lint/UnderscorePrefixedVariableName:
|
|
51
55
|
Enabled: false
|
|
@@ -94,6 +98,9 @@ Naming/MethodParameterName:
|
|
|
94
98
|
Naming/PredicateMethod:
|
|
95
99
|
Enabled: false
|
|
96
100
|
|
|
101
|
+
Naming/PredicatePrefix:
|
|
102
|
+
Enabled: false
|
|
103
|
+
|
|
97
104
|
Naming/VariableNumber:
|
|
98
105
|
Enabled: false
|
|
99
106
|
|
data/Changes
CHANGED
data/README.md
CHANGED
|
@@ -85,7 +85,7 @@ the first worksheet in an Excel XML spreadsheet called ruby.xlsx:
|
|
|
85
85
|
Original Perl module was written by John McNamara(jmcnamara@cpan.org).
|
|
86
86
|
|
|
87
87
|
Converted to ruby by Hideo NAKAMURA(nakamrua.hideo@gmail.com)
|
|
88
|
-
Copyright (c) 2012-
|
|
88
|
+
Copyright (c) 2012-2026 Hideo NAKAMURA.
|
|
89
89
|
|
|
90
90
|
See LICENSE.txt for further details.
|
|
91
91
|
|
|
@@ -24,11 +24,14 @@ module Writexlsx
|
|
|
24
24
|
@drawings = Drawings.new
|
|
25
25
|
@is_chartsheet = true
|
|
26
26
|
@chart = nil
|
|
27
|
-
@charts = [1]
|
|
28
27
|
@zoom_scale_normal = 0
|
|
29
28
|
@page_setup.orientation = false
|
|
30
29
|
end
|
|
31
30
|
|
|
31
|
+
def charts
|
|
32
|
+
[@chart].compact
|
|
33
|
+
end
|
|
34
|
+
|
|
32
35
|
#
|
|
33
36
|
# Assemble and write the XML file.
|
|
34
37
|
#
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Writexlsx
|
|
5
|
+
module ObjectPositioning
|
|
6
|
+
include Writexlsx::Utility
|
|
7
|
+
|
|
8
|
+
#
|
|
9
|
+
# Calculate the vertices that define the position of a graphical object
|
|
10
|
+
# within the worksheet in pixels.
|
|
11
|
+
#
|
|
12
|
+
def position_object_pixels(col_start, row_start, x1, y1, width, height, anchor = nil) # :nodoc:
|
|
13
|
+
context = object_positioning_context(anchor)
|
|
14
|
+
|
|
15
|
+
position_object_pixels_with_context(
|
|
16
|
+
col_start, row_start, x1, y1, width, height, context
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
# def position_object_pixels(col_start, row_start, x1, y1, width, height, anchor = nil) # :nodoc:
|
|
20
|
+
# col_start, row_start, x1, y1 =
|
|
21
|
+
# adjust_start_position_for_negative_offsets(col_start, row_start, x1, y1)
|
|
22
|
+
|
|
23
|
+
# x_abs, y_abs =
|
|
24
|
+
# calculate_absolute_position(col_start, row_start, x1, y1, anchor)
|
|
25
|
+
|
|
26
|
+
# col_start, row_start, x1, y1 =
|
|
27
|
+
# adjust_start_position_for_cell_offsets(col_start, row_start, x1, y1, anchor)
|
|
28
|
+
|
|
29
|
+
# col_end, row_end, x2, y2 =
|
|
30
|
+
# calculate_object_end_position(col_start, row_start, x1, y1, width, height, anchor)
|
|
31
|
+
|
|
32
|
+
# [col_start, row_start, x1, y1, col_end, row_end, x2, y2, x_abs, y_abs]
|
|
33
|
+
# end
|
|
34
|
+
|
|
35
|
+
#
|
|
36
|
+
# Calculate the vertices that define the position of a graphical object
|
|
37
|
+
# within the worksheet in EMUs.
|
|
38
|
+
#
|
|
39
|
+
def position_object_emus(graphical_object) # :nodoc:
|
|
40
|
+
object = graphical_object
|
|
41
|
+
col_start, row_start, x1, y1, col_end, row_end, x2, y2, x_abs, y_abs =
|
|
42
|
+
position_object_pixels(
|
|
43
|
+
object.col, object.row, object.x_offset, object.y_offset,
|
|
44
|
+
object.scaled_width, object.scaled_height, object.anchor
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Convert the pixel values to EMUs. See above.
|
|
48
|
+
x1 = (0.5 + (9_525 * x1)).to_i
|
|
49
|
+
y1 = (0.5 + (9_525 * y1)).to_i
|
|
50
|
+
x2 = (0.5 + (9_525 * x2)).to_i
|
|
51
|
+
y2 = (0.5 + (9_525 * y2)).to_i
|
|
52
|
+
x_abs = (0.5 + (9_525 * x_abs)).to_i
|
|
53
|
+
y_abs = (0.5 + (9_525 * y_abs)).to_i
|
|
54
|
+
|
|
55
|
+
[col_start, row_start, x1, y1, col_end, row_end, x2, y2, x_abs, y_abs]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
#
|
|
59
|
+
# Convert the width of a cell from pixels to character units.
|
|
60
|
+
#
|
|
61
|
+
def pixels_to_width(pixels)
|
|
62
|
+
max_digit_width = 7.0
|
|
63
|
+
padding = 5.0
|
|
64
|
+
|
|
65
|
+
if pixels <= 12
|
|
66
|
+
pixels / (max_digit_width + padding)
|
|
67
|
+
else
|
|
68
|
+
(pixels - padding) / max_digit_width
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
#
|
|
73
|
+
# Convert the height of a cell from pixels to character units.
|
|
74
|
+
#
|
|
75
|
+
def pixels_to_height(pixels)
|
|
76
|
+
height = 0.75 * pixels
|
|
77
|
+
height = height.to_i if (height - height.to_i).abs < 0.1
|
|
78
|
+
height
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def position_object_pixels_with_context(col_start, row_start, x1, y1, width, height, context)
|
|
84
|
+
col_start, row_start, x1, y1 =
|
|
85
|
+
adjust_start_position_for_negative_offsets(col_start, row_start, x1, y1, context)
|
|
86
|
+
|
|
87
|
+
x_abs, y_abs =
|
|
88
|
+
calculate_absolute_position(col_start, row_start, x1, y1, context)
|
|
89
|
+
|
|
90
|
+
col_start, row_start, x1, y1 =
|
|
91
|
+
adjust_start_position_for_cell_offsets(col_start, row_start, x1, y1, context)
|
|
92
|
+
|
|
93
|
+
col_end, row_end, x2, y2 =
|
|
94
|
+
calculate_object_end_position(col_start, row_start, x1, y1, width, height, context)
|
|
95
|
+
|
|
96
|
+
[col_start, row_start, x1, y1, col_end, row_end, x2, y2, x_abs, y_abs]
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def adjust_start_position_for_negative_offsets(col_start, row_start, x1, y1, context)
|
|
100
|
+
# Adjust start column for negative offsets.
|
|
101
|
+
while x1 < 0 && col_start > 0
|
|
102
|
+
x1 += context[:size_col].call(col_start - 1, 0)
|
|
103
|
+
col_start -= 1
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Adjust start row for negative offsets.
|
|
107
|
+
while y1 < 0 && row_start > 0
|
|
108
|
+
y1 += context[:size_row].call(row_start - 1, 0)
|
|
109
|
+
row_start -= 1
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Ensure that the image isn't shifted off the page at top left.
|
|
113
|
+
x1 = 0 if x1 < 0
|
|
114
|
+
y1 = 0 if y1 < 0
|
|
115
|
+
|
|
116
|
+
[col_start, row_start, x1, y1]
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def calculate_absolute_position(col_start, row_start, x1, y1, context)
|
|
120
|
+
# Calculate the absolute x offset of the top-left vertex.
|
|
121
|
+
x_abs = if context[:col_size_changed]
|
|
122
|
+
(0..(col_start - 1)).inject(0) do |sum, col|
|
|
123
|
+
sum + context[:size_col].call(col, context[:anchor])
|
|
124
|
+
end
|
|
125
|
+
else
|
|
126
|
+
# Optimisation for when the column widths haven't changed.
|
|
127
|
+
context[:default_col_pixels] * col_start
|
|
128
|
+
end
|
|
129
|
+
x_abs += x1
|
|
130
|
+
|
|
131
|
+
# Calculate the absolute y offset of the top-left vertex.
|
|
132
|
+
y_abs = if context[:row_size_changed]
|
|
133
|
+
(0..(row_start - 1)).inject(0) do |sum, row|
|
|
134
|
+
sum + context[:size_row].call(row, context[:anchor])
|
|
135
|
+
end
|
|
136
|
+
else
|
|
137
|
+
# Optimisation for when the row heights haven't changed.
|
|
138
|
+
context[:default_row_pixels] * row_start
|
|
139
|
+
end
|
|
140
|
+
y_abs += y1
|
|
141
|
+
|
|
142
|
+
[x_abs, y_abs]
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def adjust_start_position_for_cell_offsets(col_start, row_start, x1, y1, context)
|
|
146
|
+
# Adjust start column for offsets that are greater than the col width.
|
|
147
|
+
while x1 >= context[:size_col].call(col_start, context[:anchor])
|
|
148
|
+
x1 -= context[:size_col].call(col_start, 0)
|
|
149
|
+
col_start += 1
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Adjust start row for offsets that are greater than the row height.
|
|
153
|
+
while y1 >= context[:size_row].call(row_start, context[:anchor])
|
|
154
|
+
y1 -= context[:size_row].call(row_start, 0)
|
|
155
|
+
row_start += 1
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
[col_start, row_start, x1, y1]
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def calculate_object_end_position(col_start, row_start, x1, y1, width, height, context)
|
|
162
|
+
# Initialise end cell to the same as the start cell.
|
|
163
|
+
col_end = col_start
|
|
164
|
+
row_end = row_start
|
|
165
|
+
|
|
166
|
+
# Only offset the image in the cell if the row/col isn't hidden.
|
|
167
|
+
width += x1 if context[:size_col].call(col_start, context[:anchor]) > 0
|
|
168
|
+
height += y1 if context[:size_row].call(row_start, context[:anchor]) > 0
|
|
169
|
+
|
|
170
|
+
# Subtract the underlying cell widths to find the end cell of the object.
|
|
171
|
+
while width >= context[:size_col].call(col_end, context[:anchor])
|
|
172
|
+
width -= context[:size_col].call(col_end, context[:anchor])
|
|
173
|
+
col_end += 1
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Subtract the underlying cell heights to find the end cell of the object.
|
|
177
|
+
while height >= context[:size_row].call(row_end, context[:anchor])
|
|
178
|
+
height -= context[:size_row].call(row_end, context[:anchor])
|
|
179
|
+
row_end += 1
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# The end vertices are whatever is left from the width and height.
|
|
183
|
+
x2 = width
|
|
184
|
+
y2 = height
|
|
185
|
+
|
|
186
|
+
[col_end, row_end, x2, y2]
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Writexlsx
|
|
5
|
+
class PageSetup # :nodoc:
|
|
6
|
+
include Writexlsx::Utility
|
|
7
|
+
|
|
8
|
+
attr_accessor :margin_left, :margin_right, :margin_top, :margin_bottom # :nodoc:
|
|
9
|
+
attr_accessor :margin_header, :margin_footer # :nodoc:
|
|
10
|
+
attr_accessor :repeat_rows, :repeat_cols, :print_area # :nodoc:
|
|
11
|
+
attr_accessor :hbreaks, :vbreaks, :scale # :nodoc:
|
|
12
|
+
attr_accessor :fit_page, :fit_width, :fit_height, :page_setup_changed # :nodoc:
|
|
13
|
+
attr_writer :across # :nodoc:
|
|
14
|
+
attr_accessor :orientation, :print_options_changed, :black_white # :nodoc:
|
|
15
|
+
attr_accessor :header, :footer, :header_footer_changed, :header_footer_aligns, :header_footer_scales
|
|
16
|
+
attr_writer :page_start
|
|
17
|
+
attr_writer :horizontal_dpi, :vertical_dpi
|
|
18
|
+
|
|
19
|
+
def initialize # :nodoc:
|
|
20
|
+
@margin_left = 0.7
|
|
21
|
+
@margin_right = 0.7
|
|
22
|
+
@margin_top = 0.75
|
|
23
|
+
@margin_bottom = 0.75
|
|
24
|
+
@margin_header = 0.3
|
|
25
|
+
@margin_footer = 0.3
|
|
26
|
+
@repeat_rows = ''
|
|
27
|
+
@repeat_cols = ''
|
|
28
|
+
@print_area = ''
|
|
29
|
+
@hbreaks = []
|
|
30
|
+
@vbreaks = []
|
|
31
|
+
@scale = 100
|
|
32
|
+
@fit_page = false
|
|
33
|
+
@fit_width = nil
|
|
34
|
+
@fit_height = nil
|
|
35
|
+
@page_setup_changed = false
|
|
36
|
+
@across = false
|
|
37
|
+
@orientation = true
|
|
38
|
+
@header_footer_aligns = true
|
|
39
|
+
@header_footer_scales = true
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def paper=(paper_size)
|
|
43
|
+
if paper_size
|
|
44
|
+
@paper_size = paper_size
|
|
45
|
+
@page_setup_changed = true
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def center_horizontally
|
|
50
|
+
@print_options_changed = true
|
|
51
|
+
@hcenter = true
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def center_vertically
|
|
55
|
+
@print_options_changed = true
|
|
56
|
+
@vcenter = true
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def print_row_col_headers(headers)
|
|
60
|
+
if headers
|
|
61
|
+
@print_headers = true
|
|
62
|
+
@print_options_changed = true
|
|
63
|
+
else
|
|
64
|
+
@print_headers = false
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def hide_gridlines(option)
|
|
69
|
+
if option == 0 || !option
|
|
70
|
+
@print_gridlines = true
|
|
71
|
+
@print_options_changed = true
|
|
72
|
+
else
|
|
73
|
+
@print_gridlines = false
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
#
|
|
78
|
+
# Write the <pageSetup> element.
|
|
79
|
+
#
|
|
80
|
+
# The following is an example taken from Excel.
|
|
81
|
+
#
|
|
82
|
+
# <pageSetup
|
|
83
|
+
# paperSize="9"
|
|
84
|
+
# scale="110"
|
|
85
|
+
# fitToWidth="2"
|
|
86
|
+
# fitToHeight="2"
|
|
87
|
+
# pageOrder="overThenDown"
|
|
88
|
+
# orientation="portrait"
|
|
89
|
+
# useFirstPageNumber="1"
|
|
90
|
+
# blackAndWhite="1"
|
|
91
|
+
# draft="1"
|
|
92
|
+
# horizontalDpi="200"
|
|
93
|
+
# verticalDpi="200"
|
|
94
|
+
# r:id="rId1"
|
|
95
|
+
# />
|
|
96
|
+
#
|
|
97
|
+
def write_page_setup(writer) # :nodoc:
|
|
98
|
+
return unless @page_setup_changed
|
|
99
|
+
|
|
100
|
+
attributes = []
|
|
101
|
+
attributes << ['paperSize', @paper_size] if @paper_size
|
|
102
|
+
attributes << ['scale', @scale] if @scale != 100
|
|
103
|
+
attributes << ['fitToWidth', @fit_width] if @fit_page && @fit_width != 1
|
|
104
|
+
attributes << ['fitToHeight', @fit_height] if @fit_page && @fit_height != 1
|
|
105
|
+
attributes << %w[pageOrder overThenDown] if @across
|
|
106
|
+
attributes << ['firstPageNumber', @page_start] if @page_start && @page_start > 1
|
|
107
|
+
attributes << ['orientation',
|
|
108
|
+
if @orientation
|
|
109
|
+
'portrait'
|
|
110
|
+
else
|
|
111
|
+
'landscape'
|
|
112
|
+
end]
|
|
113
|
+
attributes << ['blackAndWhite', 1] if @black_white
|
|
114
|
+
attributes << ['useFirstPageNumber', 1] if ptrue?(@page_start)
|
|
115
|
+
|
|
116
|
+
# Set the DPI. Mainly only for testing.
|
|
117
|
+
attributes << ['horizontalDpi', @horizontal_dpi] if @horizontal_dpi
|
|
118
|
+
attributes << ['verticalDpi', @vertical_dpi] if @vertical_dpi
|
|
119
|
+
|
|
120
|
+
writer.empty_tag('pageSetup', attributes)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
#
|
|
124
|
+
# Write the <pageMargins> element.
|
|
125
|
+
#
|
|
126
|
+
def write_page_margins(writer) # :nodoc:
|
|
127
|
+
writer.empty_tag('pageMargins', margin_attributes)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
#
|
|
131
|
+
# Write the <printOptions> element.
|
|
132
|
+
#
|
|
133
|
+
def write_print_options(writer) # :nodoc:
|
|
134
|
+
return unless @print_options_changed
|
|
135
|
+
|
|
136
|
+
attributes = []
|
|
137
|
+
attributes << ['horizontalCentered', 1] if @hcenter
|
|
138
|
+
attributes << ['verticalCentered', 1] if @vcenter
|
|
139
|
+
attributes << ['headings', 1] if @print_headers
|
|
140
|
+
attributes << ['gridLines', 1] if @print_gridlines
|
|
141
|
+
writer.empty_tag('printOptions', attributes)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
#
|
|
145
|
+
# Write the <headerFooter> element.
|
|
146
|
+
#
|
|
147
|
+
def write_header_footer(writer, excel2003_style) # :nodoc:
|
|
148
|
+
tag = 'headerFooter'
|
|
149
|
+
attributes = []
|
|
150
|
+
attributes << ['scaleWithDoc', 0] unless ptrue?(@header_footer_scales)
|
|
151
|
+
attributes << ['alignWithMargins', 0] unless ptrue?(@header_footer_aligns)
|
|
152
|
+
|
|
153
|
+
if @header_footer_changed
|
|
154
|
+
writer.tag_elements(tag, attributes) do
|
|
155
|
+
write_odd_header(writer) if @header && @header != ''
|
|
156
|
+
write_odd_footer(writer) if @footer && @footer != ''
|
|
157
|
+
end
|
|
158
|
+
elsif excel2003_style
|
|
159
|
+
writer.empty_tag(tag, attributes)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
private
|
|
164
|
+
|
|
165
|
+
#
|
|
166
|
+
# Write the <oddHeader> element.
|
|
167
|
+
#
|
|
168
|
+
def write_odd_header(writer) # :nodoc:
|
|
169
|
+
writer.data_element('oddHeader', @header)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
#
|
|
173
|
+
# Write the <oddFooter> element.
|
|
174
|
+
#
|
|
175
|
+
def write_odd_footer(writer) # :nodoc:
|
|
176
|
+
writer.data_element('oddFooter', @footer)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def margin_attributes # :nodoc:
|
|
180
|
+
[
|
|
181
|
+
['left', @margin_left],
|
|
182
|
+
['right', @margin_right],
|
|
183
|
+
['top', @margin_top],
|
|
184
|
+
['bottom', @margin_bottom],
|
|
185
|
+
['header', @margin_header],
|
|
186
|
+
['footer', @margin_footer]
|
|
187
|
+
]
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
data/lib/write_xlsx/sheets.rb
CHANGED
|
@@ -91,9 +91,9 @@ module Writexlsx
|
|
|
91
91
|
def write_comment_files(package_dir)
|
|
92
92
|
self.select { |sheet| sheet.has_comments? }
|
|
93
93
|
.each_with_index do |sheet, index|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
FileUtils.mkdir_p("#{package_dir}/xl/drawings")
|
|
95
|
+
sheet.comments.set_xml_writer("#{package_dir}/xl/comments#{index + 1}.xml")
|
|
96
|
+
sheet.comments.assemble_xml_file
|
|
97
97
|
end
|
|
98
98
|
end
|
|
99
99
|
|
data/lib/write_xlsx/utility.rb
CHANGED
|
@@ -99,7 +99,7 @@ module Writexlsx
|
|
|
99
99
|
def xl_range_formula(sheetname, row_1, row_2, col_1, col_2)
|
|
100
100
|
# Use Excel's conventions and quote the sheet name if it contains any
|
|
101
101
|
# non-word character or if it isn't already quoted.
|
|
102
|
-
sheetname =
|
|
102
|
+
sheetname = quote_sheetname(sheetname)
|
|
103
103
|
|
|
104
104
|
range1 = xl_rowcol_to_cell(row_1, col_1, 1, 1)
|
|
105
105
|
range2 = xl_rowcol_to_cell(row_2, col_2, 1, 1)
|
|
@@ -128,15 +128,11 @@ module Writexlsx
|
|
|
128
128
|
# TODO. We need to handle more special cases.
|
|
129
129
|
#
|
|
130
130
|
def quote_sheetname(sheetname) # :nodoc:
|
|
131
|
-
# Use Excel's conventions and quote the sheet name if it comtains any
|
|
132
|
-
# non-word character or if it isn't already quoted.
|
|
133
131
|
name = sheetname.dup
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
end
|
|
139
|
-
name
|
|
132
|
+
return name if already_quoted_sheetname?(name)
|
|
133
|
+
return name unless sheetname_needs_quoting?(name)
|
|
134
|
+
|
|
135
|
+
"'#{escape_sheetname(name)}'"
|
|
140
136
|
end
|
|
141
137
|
|
|
142
138
|
def check_dimensions(row, col)
|
|
@@ -965,6 +961,58 @@ module Writexlsx
|
|
|
965
961
|
def write_a_end_para_rpr # :nodoc:
|
|
966
962
|
@writer.empty_tag('a:endParaRPr', [%w[lang en-US]])
|
|
967
963
|
end
|
|
964
|
+
|
|
965
|
+
private
|
|
966
|
+
|
|
967
|
+
def already_quoted_sheetname?(name)
|
|
968
|
+
name.start_with?("'")
|
|
969
|
+
end
|
|
970
|
+
|
|
971
|
+
def escape_sheetname(name)
|
|
972
|
+
name.gsub("'", "''")
|
|
973
|
+
end
|
|
974
|
+
|
|
975
|
+
def sheetname_needs_quoting?(name)
|
|
976
|
+
contains_non_identifier_chars?(name) ||
|
|
977
|
+
starts_with_digit_or_dot?(name) ||
|
|
978
|
+
valid_a1_reference_name?(name) ||
|
|
979
|
+
starts_with_rc_reference?(name) ||
|
|
980
|
+
single_rc_reference?(name)
|
|
981
|
+
end
|
|
982
|
+
|
|
983
|
+
def contains_non_identifier_chars?(name)
|
|
984
|
+
name.match?(/[^\p{L}\p{N}_.]/)
|
|
985
|
+
end
|
|
986
|
+
|
|
987
|
+
def starts_with_digit_or_dot?(name)
|
|
988
|
+
name.match?(/^[\p{N}.]/)
|
|
989
|
+
end
|
|
990
|
+
|
|
991
|
+
def valid_a1_reference_name?(name)
|
|
992
|
+
upcased = name.upcase
|
|
993
|
+
return false unless upcased.match?(/^[A-Z]{1,3}\d+$/)
|
|
994
|
+
|
|
995
|
+
row, col = xl_cell_to_rowcol(upcased)
|
|
996
|
+
row.between?(0, 1_048_575) && col.between?(0, 16_383)
|
|
997
|
+
end
|
|
998
|
+
|
|
999
|
+
def starts_with_rc_reference?(name)
|
|
1000
|
+
upcased = name.upcase
|
|
1001
|
+
|
|
1002
|
+
if (match = upcased.match(/^R(\d+)/))
|
|
1003
|
+
return match[1].to_i.between?(1, 1_048_576)
|
|
1004
|
+
end
|
|
1005
|
+
|
|
1006
|
+
if (match = upcased.match(/^R?C(\d+)/))
|
|
1007
|
+
return match[1].to_i.between?(1, 16_384)
|
|
1008
|
+
end
|
|
1009
|
+
|
|
1010
|
+
false
|
|
1011
|
+
end
|
|
1012
|
+
|
|
1013
|
+
def single_rc_reference?(name)
|
|
1014
|
+
%w[R C RC].include?(name.upcase)
|
|
1015
|
+
end
|
|
968
1016
|
end
|
|
969
1017
|
|
|
970
1018
|
module WriteDPtPoint
|
data/lib/write_xlsx/version.rb
CHANGED
data/lib/write_xlsx/workbook.rb
CHANGED
|
@@ -1303,40 +1303,40 @@ module Writexlsx
|
|
|
1303
1303
|
# Build an array of the worksheet charts including any combined charts.
|
|
1304
1304
|
@charts.collect { |chart| [chart, chart.combined] }.flatten.compact
|
|
1305
1305
|
.each do |chart|
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1306
|
+
chart.formula_ids.each do |range, id|
|
|
1307
|
+
# Skip if the series has user defined data.
|
|
1308
|
+
if chart.formula_data[id]
|
|
1309
|
+
seen_ranges[range] = chart.formula_data[id] unless seen_ranges[range]
|
|
1310
|
+
next
|
|
1311
|
+
# Check to see if the data is already cached locally.
|
|
1312
|
+
elsif seen_ranges[range]
|
|
1313
|
+
chart.formula_data[id] = seen_ranges[range]
|
|
1314
|
+
next
|
|
1315
|
+
end
|
|
1316
|
+
|
|
1317
|
+
# Convert the range formula to a sheet name and cell range.
|
|
1318
|
+
sheetname, *cells = get_chart_range(range)
|
|
1319
|
+
|
|
1320
|
+
# Skip if we couldn't parse the formula.
|
|
1321
|
+
next unless sheetname
|
|
1322
|
+
|
|
1323
|
+
# Handle non-contiguous ranges: (Sheet1!$A$1:$A$2,Sheet1!$A$4:$A$5).
|
|
1324
|
+
# We don't try to parse the ranges. We just return an empty list.
|
|
1325
|
+
if sheetname =~ /^\([^,]+,/
|
|
1326
|
+
chart.formula_data[id] = []
|
|
1327
|
+
seen_ranges[range] = []
|
|
1328
|
+
next
|
|
1329
|
+
end
|
|
1330
|
+
|
|
1331
|
+
# Raise if the name is unknown since it indicates a user error in
|
|
1332
|
+
# a chart series formula.
|
|
1333
|
+
raise "Unknown worksheet reference '#{sheetname}' in range '#{range}' passed to add_series()\n" unless worksheets[sheetname]
|
|
1334
|
+
|
|
1335
|
+
# Add the data to the chart.
|
|
1336
|
+
# And store range data locally to avoid lookup if seen agein.
|
|
1337
|
+
chart.formula_data[id] =
|
|
1338
|
+
seen_ranges[range] = chart_data(worksheets[sheetname], cells)
|
|
1339
|
+
end
|
|
1340
1340
|
end
|
|
1341
1341
|
end
|
|
1342
1342
|
|