write_xlsx 1.13.0 → 1.15.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/.rubocop.yml +25 -4
- data/Changes +23 -0
- data/lib/write_xlsx/chart/area.rb +2 -2
- data/lib/write_xlsx/chart/axis.rb +55 -32
- data/lib/write_xlsx/chart/axis_writer.rb +528 -0
- data/lib/write_xlsx/chart/bar.rb +2 -2
- data/lib/write_xlsx/chart/caption.rb +16 -9
- data/lib/write_xlsx/chart/chart_area.rb +121 -0
- data/lib/write_xlsx/chart/column.rb +2 -2
- data/lib/write_xlsx/chart/d_pt_point_writer.rb +14 -0
- data/lib/write_xlsx/chart/doughnut.rb +0 -3
- data/lib/write_xlsx/chart/formatting_writer.rb +652 -0
- data/lib/write_xlsx/chart/initialization.rb +100 -0
- data/lib/write_xlsx/chart/line.rb +4 -3
- data/lib/write_xlsx/chart/pie.rb +6 -2
- data/lib/write_xlsx/chart/radar.rb +2 -2
- data/lib/write_xlsx/chart/scatter.rb +4 -3
- data/lib/write_xlsx/chart/series.rb +35 -15
- data/lib/write_xlsx/chart/series_data.rb +132 -0
- data/lib/write_xlsx/chart/series_writer.rb +318 -0
- data/lib/write_xlsx/chart/settings.rb +226 -0
- data/lib/write_xlsx/chart/stock.rb +2 -2
- data/lib/write_xlsx/chart/table.rb +50 -0
- data/lib/write_xlsx/chart/xml_writer.rb +305 -0
- data/lib/write_xlsx/chart.rb +286 -2477
- data/lib/write_xlsx/chartsheet.rb +31 -82
- data/lib/write_xlsx/constants.rb +11 -0
- data/lib/write_xlsx/drawing.rb +5 -3
- data/lib/write_xlsx/format/alignment_state.rb +39 -0
- data/lib/write_xlsx/format/alignment_style.rb +92 -0
- data/lib/write_xlsx/format/border_state.rb +47 -0
- data/lib/write_xlsx/format/border_style.rb +116 -0
- data/lib/write_xlsx/format/fill_state.rb +26 -0
- data/lib/write_xlsx/format/fill_style.rb +52 -0
- data/lib/write_xlsx/format/font_state.rb +74 -0
- data/lib/write_xlsx/format/font_style.rb +172 -0
- data/lib/write_xlsx/format/format_state.rb +65 -0
- data/lib/write_xlsx/format/number_format_state.rb +20 -0
- data/lib/write_xlsx/format/number_format_style.rb +28 -0
- data/lib/write_xlsx/format/protection_state.rb +20 -0
- data/lib/write_xlsx/format/protection_style.rb +28 -0
- data/lib/write_xlsx/format.rb +1093 -426
- data/lib/write_xlsx/formats.rb +0 -2
- data/lib/write_xlsx/image_property.rb +4 -1
- data/lib/write_xlsx/inserted_chart.rb +1 -1
- data/lib/write_xlsx/object_positioning.rb +15 -1
- data/lib/write_xlsx/package/app.rb +2 -2
- data/lib/write_xlsx/package/button.rb +6 -2
- data/lib/write_xlsx/package/comments.rb +11 -3
- data/lib/write_xlsx/package/conditional_format.rb +7 -3
- data/lib/write_xlsx/package/content_types.rb +2 -2
- data/lib/write_xlsx/package/core.rb +2 -2
- data/lib/write_xlsx/package/custom.rb +3 -2
- data/lib/write_xlsx/package/metadata.rb +2 -2
- data/lib/write_xlsx/package/packager.rb +0 -3
- data/lib/write_xlsx/package/relationships.rb +2 -2
- data/lib/write_xlsx/package/rich_value.rb +4 -2
- data/lib/write_xlsx/package/rich_value_rel.rb +2 -2
- data/lib/write_xlsx/package/rich_value_structure.rb +2 -2
- data/lib/write_xlsx/package/rich_value_types.rb +3 -3
- data/lib/write_xlsx/package/shared_strings.rb +2 -2
- data/lib/write_xlsx/package/styles.rb +13 -9
- data/lib/write_xlsx/package/table.rb +8 -2
- data/lib/write_xlsx/package/theme.rb +0 -3
- data/lib/write_xlsx/package/vml.rb +2 -2
- data/lib/write_xlsx/page_setup.rb +3 -1
- data/lib/write_xlsx/shape.rb +97 -100
- data/lib/write_xlsx/sheets.rb +6 -1
- data/lib/write_xlsx/sparkline.rb +2 -2
- data/lib/write_xlsx/utility/cell_reference.rb +124 -0
- data/lib/write_xlsx/utility/chart_formatting.rb +262 -0
- data/lib/write_xlsx/utility/common.rb +44 -0
- data/lib/write_xlsx/utility/date_time.rb +113 -0
- data/lib/write_xlsx/utility/dimensions.rb +40 -0
- data/lib/write_xlsx/utility/drawing.rb +136 -0
- data/lib/write_xlsx/utility/rich_text.rb +184 -0
- data/lib/write_xlsx/utility/sheetname_quoting.rb +73 -0
- data/lib/write_xlsx/utility/string_width.rb +45 -0
- data/lib/write_xlsx/utility/url.rb +27 -0
- data/lib/write_xlsx/utility/xml_primitives.rb +32 -0
- data/lib/write_xlsx/version.rb +1 -1
- data/lib/write_xlsx/workbook/chart_data.rb +188 -0
- data/lib/write_xlsx/workbook/format_preparation.rb +199 -0
- data/lib/write_xlsx/workbook/initialization.rb +223 -0
- data/lib/write_xlsx/workbook/package_preparation.rb +231 -0
- data/lib/write_xlsx/workbook/workbook_writer.rb +164 -0
- data/lib/write_xlsx/workbook.rb +143 -981
- data/lib/write_xlsx/worksheet/autofilter.rb +3 -1
- data/lib/write_xlsx/worksheet/cell_data.rb +5 -1
- data/lib/write_xlsx/worksheet/columns.rb +8 -3
- data/lib/write_xlsx/worksheet/data_validation.rb +9 -1
- data/lib/write_xlsx/worksheet/data_writing.rb +42 -15
- data/lib/write_xlsx/worksheet/drawing_preparation.rb +1 -1
- data/lib/write_xlsx/worksheet/formatting.rb +3 -1
- data/lib/write_xlsx/worksheet/hyperlink.rb +9 -1
- data/lib/write_xlsx/worksheet/row_col_sizing.rb +3 -1
- data/lib/write_xlsx/worksheet/xml_writer.rb +9 -4
- data/lib/write_xlsx/worksheet.rb +19 -2
- metadata +42 -3
- data/lib/write_xlsx/utility.rb +0 -1034
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Writexlsx
|
|
5
|
+
module Utility
|
|
6
|
+
module DateTime
|
|
7
|
+
#
|
|
8
|
+
# convert_date_time(date_time_string)
|
|
9
|
+
#
|
|
10
|
+
# The function takes a date and time in ISO8601 "yyyy-mm-ddThh:mm:ss.ss" format
|
|
11
|
+
# and converts it to a decimal number representing a valid Excel date.
|
|
12
|
+
#
|
|
13
|
+
def convert_date_time(date_time_string) # :nodoc:
|
|
14
|
+
date_time = date_time_string.to_s.sub(/^\s+/, '').sub(/\s+$/, '').sub(/Z$/, '')
|
|
15
|
+
|
|
16
|
+
# Check for invalid date char.
|
|
17
|
+
return nil if date_time =~ /[^0-9T:\-.Z]/
|
|
18
|
+
|
|
19
|
+
# Check for "T" after date or before time.
|
|
20
|
+
return nil unless date_time =~ /\dT|T\d/
|
|
21
|
+
|
|
22
|
+
days = 0 # Number of days since epoch
|
|
23
|
+
seconds = 0 # Time expressed as fraction of 24h hours in seconds
|
|
24
|
+
|
|
25
|
+
# Split into date and time.
|
|
26
|
+
date, time = date_time.split("T")
|
|
27
|
+
|
|
28
|
+
# We allow the time portion of the input DateTime to be optional.
|
|
29
|
+
if time
|
|
30
|
+
# Match hh:mm:ss.sss+ where the seconds are optional
|
|
31
|
+
if time =~ /^(\d\d):(\d\d)(:(\d\d(\.\d+)?))?/
|
|
32
|
+
hour = ::Regexp.last_match(1).to_i
|
|
33
|
+
min = ::Regexp.last_match(2).to_i
|
|
34
|
+
sec = ::Regexp.last_match(4).to_f
|
|
35
|
+
else
|
|
36
|
+
return nil # Not a valid time format.
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Some boundary checks
|
|
40
|
+
return nil if hour >= 24
|
|
41
|
+
return nil if min >= 60
|
|
42
|
+
return nil if sec >= 60
|
|
43
|
+
|
|
44
|
+
# Excel expresses seconds as a fraction of the number in 24 hours.
|
|
45
|
+
seconds = ((hour * 60 * 60) + (min * 60) + sec) / (24.0 * 60 * 60)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# We allow the date portion of the input DateTime to be optional.
|
|
49
|
+
return seconds if date == ''
|
|
50
|
+
|
|
51
|
+
# Match date as yyyy-mm-dd.
|
|
52
|
+
if date =~ /^(\d\d\d\d)-(\d\d)-(\d\d)$/
|
|
53
|
+
year = ::Regexp.last_match(1).to_i
|
|
54
|
+
month = ::Regexp.last_match(2).to_i
|
|
55
|
+
day = ::Regexp.last_match(3).to_i
|
|
56
|
+
else
|
|
57
|
+
return nil # Not a valid date format.
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Set the epoch as 1900 or 1904. Defaults to 1900.
|
|
61
|
+
# Special cases for Excel.
|
|
62
|
+
unless date_1904?
|
|
63
|
+
return seconds if date == '1899-12-31' # Excel 1900 epoch
|
|
64
|
+
return seconds if date == '1900-01-00' # Excel 1900 epoch
|
|
65
|
+
return 60 + seconds if date == '1900-02-29' # Excel false leapday
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# We calculate the date by calculating the number of days since the epoch
|
|
69
|
+
# and adjust for the number of leap days. We calculate the number of leap
|
|
70
|
+
# days by normalising the year in relation to the epoch. Thus the year 2000
|
|
71
|
+
# becomes 100 for 4 and 100 year leapdays and 400 for 400 year leapdays.
|
|
72
|
+
#
|
|
73
|
+
epoch = date_1904? ? 1904 : 1900
|
|
74
|
+
offset = date_1904? ? 4 : 0
|
|
75
|
+
norm = 300
|
|
76
|
+
range = year - epoch
|
|
77
|
+
|
|
78
|
+
# Set month days and check for leap year.
|
|
79
|
+
mdays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
|
80
|
+
leap = 0
|
|
81
|
+
leap = 1 if (year % 4 == 0 && year % 100 != 0) || year % 400 == 0
|
|
82
|
+
mdays[1] = 29 if leap != 0
|
|
83
|
+
|
|
84
|
+
# Some boundary checks
|
|
85
|
+
return nil if year < epoch || year > 9999
|
|
86
|
+
return nil if month < 1 || month > 12
|
|
87
|
+
return nil if day < 1 || day > mdays[month - 1]
|
|
88
|
+
|
|
89
|
+
# Accumulate the number of days since the epoch.
|
|
90
|
+
days = day # Add days for current month
|
|
91
|
+
(0..(month - 2)).each do |m|
|
|
92
|
+
days += mdays[m] # Add days for past months
|
|
93
|
+
end
|
|
94
|
+
days += range * 365 # Add days for past years
|
|
95
|
+
days += (range / 4) # Add leapdays
|
|
96
|
+
days -= ((range + offset) / 100) # Subtract 100 year leapdays
|
|
97
|
+
days += ((range + offset + norm) / 400) # Add 400 year leapdays
|
|
98
|
+
days -= leap # Already counted above
|
|
99
|
+
|
|
100
|
+
# Adjust for Excel erroneously treating 1900 as a leap year.
|
|
101
|
+
days += 1 if !date_1904? && days > 59
|
|
102
|
+
|
|
103
|
+
date_time = sprintf("%0.10f", days + seconds)
|
|
104
|
+
date_time = date_time.sub(/\.?0+$/, '') if date_time =~ /\./
|
|
105
|
+
if date_time =~ /\./
|
|
106
|
+
date_time.to_f
|
|
107
|
+
else
|
|
108
|
+
date_time.to_i
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'write_xlsx/constants'
|
|
5
|
+
|
|
6
|
+
module Writexlsx
|
|
7
|
+
module Utility
|
|
8
|
+
module Dimensions
|
|
9
|
+
include Constants
|
|
10
|
+
|
|
11
|
+
def check_dimensions(row, col)
|
|
12
|
+
raise WriteXLSXDimensionError if !row || row >= ROW_MAX || !col || col >= COL_MAX
|
|
13
|
+
|
|
14
|
+
0
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
#
|
|
18
|
+
# Check that row and col are valid and store max and min values for use in
|
|
19
|
+
# other methods/elements.
|
|
20
|
+
#
|
|
21
|
+
def check_dimensions_and_update_max_min_values(row, col, ignore_row = 0, ignore_col = 0) # :nodoc:
|
|
22
|
+
check_dimensions(row, col)
|
|
23
|
+
store_row_max_min_values(row) if ignore_row == 0
|
|
24
|
+
store_col_max_min_values(col) if ignore_col == 0
|
|
25
|
+
|
|
26
|
+
0
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def store_row_max_min_values(row)
|
|
30
|
+
@dim_rowmin = row if !@dim_rowmin || (row < @dim_rowmin)
|
|
31
|
+
@dim_rowmax = row if !@dim_rowmax || (row > @dim_rowmax)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def store_col_max_min_values(col)
|
|
35
|
+
@dim_colmin = col if !@dim_colmin || (col < @dim_colmin)
|
|
36
|
+
@dim_colmax = col if !@dim_colmax || (col > @dim_colmax)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Writexlsx
|
|
5
|
+
module Utility
|
|
6
|
+
module Drawing
|
|
7
|
+
#
|
|
8
|
+
# Convert vertices from pixels to points.
|
|
9
|
+
#
|
|
10
|
+
def pixels_to_points(vertices)
|
|
11
|
+
_col_start, _row_start, _x1, _y1,
|
|
12
|
+
_col_end, _row_end, _x2, _y2,
|
|
13
|
+
left, top, width, height = vertices.flatten
|
|
14
|
+
|
|
15
|
+
left *= 0.75
|
|
16
|
+
top *= 0.75
|
|
17
|
+
width *= 0.75
|
|
18
|
+
height *= 0.75
|
|
19
|
+
|
|
20
|
+
[left, top, width, height]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def v_shape_attributes_base(id)
|
|
24
|
+
[
|
|
25
|
+
['id', "_x0000_s#{id}"],
|
|
26
|
+
['type', type]
|
|
27
|
+
]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def v_shape_style_base(z_index, vertices)
|
|
31
|
+
left, top, width, height = pixels_to_points(vertices)
|
|
32
|
+
|
|
33
|
+
left_str = float_to_str(left)
|
|
34
|
+
top_str = float_to_str(top)
|
|
35
|
+
width_str = float_to_str(width)
|
|
36
|
+
height_str = float_to_str(height)
|
|
37
|
+
z_index_str = float_to_str(z_index)
|
|
38
|
+
|
|
39
|
+
shape_style_base(left_str, top_str, width_str, height_str, z_index_str)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def shape_style_base(left_str, top_str, width_str, height_str, z_index_str)
|
|
43
|
+
[
|
|
44
|
+
'position:absolute;',
|
|
45
|
+
'margin-left:',
|
|
46
|
+
left_str, 'pt;',
|
|
47
|
+
'margin-top:',
|
|
48
|
+
top_str, 'pt;',
|
|
49
|
+
'width:',
|
|
50
|
+
width_str, 'pt;',
|
|
51
|
+
'height:',
|
|
52
|
+
height_str, 'pt;',
|
|
53
|
+
'z-index:',
|
|
54
|
+
z_index_str, ';'
|
|
55
|
+
]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
#
|
|
59
|
+
# Write the <v:fill> element.
|
|
60
|
+
#
|
|
61
|
+
def write_fill
|
|
62
|
+
@writer.empty_tag('v:fill', fill_attributes)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
#
|
|
66
|
+
# Write the <v:path> element.
|
|
67
|
+
#
|
|
68
|
+
def write_comment_path(gradientshapeok, connecttype)
|
|
69
|
+
attributes = []
|
|
70
|
+
|
|
71
|
+
attributes << %w[gradientshapeok t] if gradientshapeok
|
|
72
|
+
attributes << ['o:connecttype', connecttype]
|
|
73
|
+
|
|
74
|
+
@writer.empty_tag('v:path', attributes)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
#
|
|
78
|
+
# Write the <x:Anchor> element.
|
|
79
|
+
#
|
|
80
|
+
def write_anchor
|
|
81
|
+
col_start, row_start, x1, y1, col_end, row_end, x2, y2 = @vertices
|
|
82
|
+
data = [col_start, x1, row_start, y1, col_end, x2, row_end, y2].join(', ')
|
|
83
|
+
|
|
84
|
+
@writer.data_element('x:Anchor', data)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
#
|
|
88
|
+
# Write the <x:AutoFill> element.
|
|
89
|
+
#
|
|
90
|
+
def write_auto_fill
|
|
91
|
+
@writer.data_element('x:AutoFill', 'False')
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
#
|
|
95
|
+
# Write the <div> element.
|
|
96
|
+
#
|
|
97
|
+
def write_div(align, font = nil)
|
|
98
|
+
style = "text-align:#{align}"
|
|
99
|
+
attributes = [['style', style]]
|
|
100
|
+
|
|
101
|
+
@writer.tag_elements('div', attributes) do
|
|
102
|
+
if font
|
|
103
|
+
# Write the font element.
|
|
104
|
+
write_font(font)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
#
|
|
110
|
+
# Write the <font> element.
|
|
111
|
+
#
|
|
112
|
+
def write_font(font)
|
|
113
|
+
caption = font[:_caption]
|
|
114
|
+
face = 'Calibri'
|
|
115
|
+
size = 220
|
|
116
|
+
color = '#000000'
|
|
117
|
+
|
|
118
|
+
attributes = [
|
|
119
|
+
['face', face],
|
|
120
|
+
['size', size],
|
|
121
|
+
['color', color]
|
|
122
|
+
]
|
|
123
|
+
@writer.data_element('font', caption, attributes)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
#
|
|
127
|
+
# Write the <v:stroke> element.
|
|
128
|
+
#
|
|
129
|
+
def write_stroke
|
|
130
|
+
attributes = [%w[joinstyle miter]]
|
|
131
|
+
|
|
132
|
+
@writer.empty_tag('v:stroke', attributes)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Writexlsx
|
|
5
|
+
module Utility
|
|
6
|
+
module RichText
|
|
7
|
+
def underline_attributes(underline)
|
|
8
|
+
case underline
|
|
9
|
+
when 2
|
|
10
|
+
[%w[val double]]
|
|
11
|
+
when 33
|
|
12
|
+
[%w[val singleAccounting]]
|
|
13
|
+
when 34
|
|
14
|
+
[%w[val doubleAccounting]]
|
|
15
|
+
else
|
|
16
|
+
[] # Default to single underline.
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
#
|
|
21
|
+
# Convert user defined font values into private hash values.
|
|
22
|
+
#
|
|
23
|
+
def convert_font_args(params)
|
|
24
|
+
return unless params
|
|
25
|
+
|
|
26
|
+
font = params_to_font(params)
|
|
27
|
+
|
|
28
|
+
# Convert font size units.
|
|
29
|
+
font[:_size] *= 100 if font[:_size] && font[:_size] != 0
|
|
30
|
+
|
|
31
|
+
# Convert rotation into 60,000ths of a degree.
|
|
32
|
+
font[:_rotation] = 60_000 * font[:_rotation].to_i if ptrue?(font[:_rotation])
|
|
33
|
+
|
|
34
|
+
font
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def params_to_font(params)
|
|
38
|
+
{
|
|
39
|
+
_name: params[:name],
|
|
40
|
+
_color: params[:color],
|
|
41
|
+
_size: params[:size],
|
|
42
|
+
_bold: params[:bold],
|
|
43
|
+
_italic: params[:italic],
|
|
44
|
+
_underline: params[:underline],
|
|
45
|
+
_pitch_family: params[:pitch_family],
|
|
46
|
+
_charset: params[:charset],
|
|
47
|
+
_baseline: params[:baseline] || 0,
|
|
48
|
+
_rotation: params[:rotation]
|
|
49
|
+
}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
#
|
|
53
|
+
# Get the font style attributes from a font hash.
|
|
54
|
+
#
|
|
55
|
+
def get_font_style_attributes(font)
|
|
56
|
+
return [] unless font
|
|
57
|
+
return [] unless font.respond_to?(:[])
|
|
58
|
+
|
|
59
|
+
attributes = []
|
|
60
|
+
attributes << ['sz', font[:_size]] if ptrue?(font[:_size])
|
|
61
|
+
attributes << ['b', font[:_bold]] if font[:_bold]
|
|
62
|
+
attributes << ['i', font[:_italic]] if font[:_italic]
|
|
63
|
+
attributes << %w[u sng] if font[:_underline]
|
|
64
|
+
|
|
65
|
+
# Turn off baseline when testing fonts that don't have it.
|
|
66
|
+
attributes << ['baseline', font[:_baseline]] if font[:_baseline] != -1
|
|
67
|
+
attributes
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
#
|
|
71
|
+
# Get the font latin attributes from a font hash.
|
|
72
|
+
#
|
|
73
|
+
def get_font_latin_attributes(font)
|
|
74
|
+
return [] unless font
|
|
75
|
+
return [] unless font.respond_to?(:[])
|
|
76
|
+
|
|
77
|
+
attributes = []
|
|
78
|
+
attributes << ['typeface', font[:_name]] if ptrue?(font[:_name])
|
|
79
|
+
attributes << ['pitchFamily', font[:_pitch_family]] if font[:_pitch_family]
|
|
80
|
+
attributes << ['charset', font[:_charset]] if font[:_charset]
|
|
81
|
+
|
|
82
|
+
attributes
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
#
|
|
86
|
+
# Write the <c:txPr> element.
|
|
87
|
+
#
|
|
88
|
+
def write_tx_pr(font, is_y_axis = nil) # :nodoc:
|
|
89
|
+
rotation = nil
|
|
90
|
+
rotation = font[:_rotation] if font && font.respond_to?(:[]) && font[:_rotation]
|
|
91
|
+
@writer.tag_elements('c:txPr') do
|
|
92
|
+
# Write the a:bodyPr element.
|
|
93
|
+
write_a_body_pr(rotation, is_y_axis)
|
|
94
|
+
# Write the a:lstStyle element.
|
|
95
|
+
write_a_lst_style
|
|
96
|
+
# Write the a:p element.
|
|
97
|
+
write_a_p_formula(font)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
#
|
|
102
|
+
# Write the <a:bodyPr> element.
|
|
103
|
+
#
|
|
104
|
+
def write_a_body_pr(rot, is_y_axis = nil) # :nodoc:
|
|
105
|
+
rot = -5400000 if !rot && ptrue?(is_y_axis)
|
|
106
|
+
attributes = []
|
|
107
|
+
if rot
|
|
108
|
+
if rot == 16_200_000
|
|
109
|
+
# 270 deg/stacked angle.
|
|
110
|
+
attributes << ['rot', 0]
|
|
111
|
+
attributes << %w[vert wordArtVert]
|
|
112
|
+
elsif rot == 16_260_000
|
|
113
|
+
# 271 deg/stacked angle.
|
|
114
|
+
attributes << ['rot', 0]
|
|
115
|
+
attributes << %w[vert eaVert]
|
|
116
|
+
else
|
|
117
|
+
attributes << ['rot', rot]
|
|
118
|
+
attributes << %w[vert horz]
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
@writer.empty_tag('a:bodyPr', attributes)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
#
|
|
126
|
+
# Write the <a:lstStyle> element.
|
|
127
|
+
#
|
|
128
|
+
def write_a_lst_style # :nodoc:
|
|
129
|
+
@writer.empty_tag('a:lstStyle')
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
#
|
|
133
|
+
# Write the <a:p> element for formula titles.
|
|
134
|
+
#
|
|
135
|
+
def write_a_p_formula(font = nil) # :nodoc:
|
|
136
|
+
@writer.tag_elements('a:p') do
|
|
137
|
+
# Write the a:pPr element.
|
|
138
|
+
write_a_p_pr_formula(font)
|
|
139
|
+
# Write the a:endParaRPr element.
|
|
140
|
+
write_a_end_para_rpr
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
#
|
|
145
|
+
# Write the <a:pPr> element for formula titles.
|
|
146
|
+
#
|
|
147
|
+
def write_a_p_pr_formula(font) # :nodoc:
|
|
148
|
+
@writer.tag_elements('a:pPr') { write_a_def_rpr(font) }
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
#
|
|
152
|
+
# Write the <a:defRPr> element.
|
|
153
|
+
#
|
|
154
|
+
def write_a_def_rpr(font = nil) # :nodoc:
|
|
155
|
+
write_def_rpr_r_pr_common(
|
|
156
|
+
font,
|
|
157
|
+
get_font_style_attributes(font),
|
|
158
|
+
'a:defRPr'
|
|
159
|
+
)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def write_def_rpr_r_pr_common(font, style_attributes, tag) # :nodoc:
|
|
163
|
+
latin_attributes = get_font_latin_attributes(font)
|
|
164
|
+
has_color = ptrue?(font) && ptrue?(font[:_color])
|
|
165
|
+
|
|
166
|
+
if !latin_attributes.empty? || has_color
|
|
167
|
+
@writer.tag_elements(tag, style_attributes) do
|
|
168
|
+
write_a_solid_fill(color: font[:_color]) if has_color
|
|
169
|
+
write_a_latin(latin_attributes) unless latin_attributes.empty?
|
|
170
|
+
end
|
|
171
|
+
else
|
|
172
|
+
@writer.empty_tag(tag, style_attributes)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
#
|
|
177
|
+
# Write the <a:endParaRPr> element.
|
|
178
|
+
#
|
|
179
|
+
def write_a_end_para_rpr # :nodoc:
|
|
180
|
+
@writer.empty_tag('a:endParaRPr', [%w[lang en-US]])
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Writexlsx
|
|
5
|
+
module Utility
|
|
6
|
+
module SheetnameQuoting
|
|
7
|
+
#
|
|
8
|
+
# Sheetnames used in references should be quoted if they contain any spaces,
|
|
9
|
+
# special characters or if the look like something that isn't a sheet name.
|
|
10
|
+
# TODO. We need to handle more special cases.
|
|
11
|
+
#
|
|
12
|
+
def quote_sheetname(sheetname) # :nodoc:
|
|
13
|
+
name = sheetname.dup
|
|
14
|
+
return name if already_quoted_sheetname?(name)
|
|
15
|
+
return name unless sheetname_needs_quoting?(name)
|
|
16
|
+
|
|
17
|
+
"'#{escape_sheetname(name)}'"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def already_quoted_sheetname?(name)
|
|
23
|
+
name.start_with?("'")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def escape_sheetname(name)
|
|
27
|
+
name.gsub("'", "''")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def sheetname_needs_quoting?(name)
|
|
31
|
+
contains_non_identifier_chars?(name) ||
|
|
32
|
+
starts_with_digit_or_dot?(name) ||
|
|
33
|
+
valid_a1_reference_name?(name) ||
|
|
34
|
+
starts_with_rc_reference?(name) ||
|
|
35
|
+
single_rc_reference?(name)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def contains_non_identifier_chars?(name)
|
|
39
|
+
name.match?(/[^\p{L}\p{N}_.]/)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def starts_with_digit_or_dot?(name)
|
|
43
|
+
name.match?(/^[\p{N}.]/)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def valid_a1_reference_name?(name)
|
|
47
|
+
upcased = name.upcase
|
|
48
|
+
return false unless upcased.match?(/^[A-Z]{1,3}\d+$/)
|
|
49
|
+
|
|
50
|
+
row, col = xl_cell_to_rowcol(upcased)
|
|
51
|
+
row.between?(0, 1_048_575) && col.between?(0, 16_383)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def starts_with_rc_reference?(name)
|
|
55
|
+
upcased = name.upcase
|
|
56
|
+
|
|
57
|
+
if (match = upcased.match(/^R(\d+)/))
|
|
58
|
+
return match[1].to_i.between?(1, 1_048_576)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
if (match = upcased.match(/^R?C(\d+)/))
|
|
62
|
+
return match[1].to_i.between?(1, 16_384)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
false
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def single_rc_reference?(name)
|
|
69
|
+
%w[R C RC].include?(name.upcase)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Writexlsx
|
|
5
|
+
module Utility
|
|
6
|
+
module StringWidth
|
|
7
|
+
MAX_DIGIT_WIDTH = 7 # For Calabri 11. # :nodoc:
|
|
8
|
+
PADDING = 5 # :nodoc:
|
|
9
|
+
DEFAULT_COL_PIXELS = 64
|
|
10
|
+
CHAR_WIDTHS = {
|
|
11
|
+
' ' => 3, '!' => 5, '"' => 6, '#' => 7, '$' => 7, '%' => 11,
|
|
12
|
+
'&' => 10, "'" => 3, '(' => 5, ')' => 5, '*' => 7, '+' => 7,
|
|
13
|
+
',' => 4, '-' => 5, '.' => 4, '/' => 6, '0' => 7, '1' => 7,
|
|
14
|
+
'2' => 7, '3' => 7, '4' => 7, '5' => 7, '6' => 7, '7' => 7,
|
|
15
|
+
'8' => 7, '9' => 7, ':' => 4, ';' => 4, '<' => 7, '=' => 7,
|
|
16
|
+
'>' => 7, '?' => 7, '@' => 13, 'A' => 9, 'B' => 8, 'C' => 8,
|
|
17
|
+
'D' => 9, 'E' => 7, 'F' => 7, 'G' => 9, 'H' => 9, 'I' => 4,
|
|
18
|
+
'J' => 5, 'K' => 8, 'L' => 6, 'M' => 12, 'N' => 10, 'O' => 10,
|
|
19
|
+
'P' => 8, 'Q' => 10, 'R' => 8, 'S' => 7, 'T' => 7, 'U' => 9,
|
|
20
|
+
'V' => 9, 'W' => 13, 'X' => 8, 'Y' => 7, 'Z' => 7, '[' => 5,
|
|
21
|
+
'\\' => 6, ']' => 5, '^' => 7, '_' => 7, '`' => 4, 'a' => 7,
|
|
22
|
+
'b' => 8, 'c' => 6, 'd' => 8, 'e' => 8, 'f' => 5, 'g' => 7,
|
|
23
|
+
'h' => 8, 'i' => 4, 'j' => 4, 'k' => 7, 'l' => 4, 'm' => 12,
|
|
24
|
+
'n' => 8, 'o' => 8, 'p' => 8, 'q' => 8, 'r' => 5, 's' => 6,
|
|
25
|
+
't' => 5, 'u' => 8, 'v' => 7, 'w' => 11, 'x' => 7, 'y' => 7,
|
|
26
|
+
'z' => 6, '{' => 5, '|' => 7, '}' => 5, '~' => 7
|
|
27
|
+
}.freeze
|
|
28
|
+
|
|
29
|
+
#
|
|
30
|
+
# xl_string_pixel_width($string)
|
|
31
|
+
#
|
|
32
|
+
# Get the pixel width of a string based on individual character widths taken
|
|
33
|
+
# from Excel. UTF8 characters are given a default width of 8.
|
|
34
|
+
#
|
|
35
|
+
# Note, Excel adds an additional 7 pixels padding to a cell.
|
|
36
|
+
#
|
|
37
|
+
def xl_string_pixel_width(string)
|
|
38
|
+
length = 0
|
|
39
|
+
string.to_s.chars.each { |char| length += CHAR_WIDTHS[char] || 8 }
|
|
40
|
+
|
|
41
|
+
length
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Writexlsx
|
|
5
|
+
module Utility
|
|
6
|
+
module Url
|
|
7
|
+
def escape_url(url)
|
|
8
|
+
unless url =~ /%[0-9a-fA-F]{2}/
|
|
9
|
+
# Escape the URL escape symbol.
|
|
10
|
+
url = url.gsub("%", "%25")
|
|
11
|
+
|
|
12
|
+
# Escape whitespae in URL.
|
|
13
|
+
url = url.gsub(/[\s\x00]/, '%20')
|
|
14
|
+
|
|
15
|
+
# Escape other special characters in URL.
|
|
16
|
+
re = /(["<>\[\]`^{}])/
|
|
17
|
+
while re =~ url
|
|
18
|
+
match = $LAST_MATCH_INFO[1]
|
|
19
|
+
url = url.sub(re, sprintf("%%%x", match.ord))
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
url
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Writexlsx
|
|
5
|
+
module Utility
|
|
6
|
+
module XmlPrimitives
|
|
7
|
+
#
|
|
8
|
+
# Write the <color> element.
|
|
9
|
+
#
|
|
10
|
+
def write_color(name, value, writer = @writer) # :nodoc:
|
|
11
|
+
attributes = [[name, value]]
|
|
12
|
+
|
|
13
|
+
writer.empty_tag('color', attributes)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def write_xml_declaration
|
|
17
|
+
@writer.xml_decl
|
|
18
|
+
yield
|
|
19
|
+
@writer.crlf
|
|
20
|
+
@writer.close
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def r_id_attributes(id)
|
|
24
|
+
['r:id', "rId#{id}"]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def xml_str
|
|
28
|
+
@writer.string
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
data/lib/write_xlsx/version.rb
CHANGED