write_xlsx 1.11.2 → 1.12.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/Changes +30 -0
- data/README.md +1 -1
- data/lib/write_xlsx/format.rb +5 -5
- data/lib/write_xlsx/package/content_types.rb +22 -0
- data/lib/write_xlsx/package/metadata.rb +139 -22
- data/lib/write_xlsx/package/packager.rb +120 -3
- data/lib/write_xlsx/package/relationships.rb +25 -0
- data/lib/write_xlsx/package/rich_value.rb +70 -0
- data/lib/write_xlsx/package/rich_value_rel.rb +70 -0
- data/lib/write_xlsx/package/rich_value_structure.rb +83 -0
- data/lib/write_xlsx/package/rich_value_types.rb +103 -0
- data/lib/write_xlsx/utility.rb +172 -0
- data/lib/write_xlsx/version.rb +1 -1
- data/lib/write_xlsx/workbook.rb +46 -166
- data/lib/write_xlsx/worksheet/cell_data.rb +19 -0
- data/lib/write_xlsx/worksheet/hyperlink.rb +2 -1
- data/lib/write_xlsx/worksheet.rb +93 -22
- metadata +7 -3
@@ -0,0 +1,70 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'write_xlsx/package/xml_writer_simple'
|
5
|
+
require 'write_xlsx/utility'
|
6
|
+
|
7
|
+
module Writexlsx
|
8
|
+
module Package
|
9
|
+
#
|
10
|
+
# RichValueRel - A class for writing the Excel XLSX richValueRel.xml file.
|
11
|
+
#
|
12
|
+
# Used in conjunction with Excel::Writer::XLSX
|
13
|
+
#
|
14
|
+
# Copyright 2000-2024, John McNamara, jmcnamara@cpan.org
|
15
|
+
#
|
16
|
+
# Convert to Ruby by Hideo Nakamura, nakamura.hideo@gmail.com
|
17
|
+
#
|
18
|
+
class RichValueRel
|
19
|
+
include Writexlsx::Utility
|
20
|
+
|
21
|
+
attr_writer :value_count
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
@writer = Package::XMLWriterSimple.new
|
25
|
+
@value_count = 0
|
26
|
+
end
|
27
|
+
|
28
|
+
def set_xml_writer(filename)
|
29
|
+
@writer.set_xml_writer(filename)
|
30
|
+
end
|
31
|
+
|
32
|
+
def assemble_xml_file
|
33
|
+
write_xml_declaration do
|
34
|
+
write_rich_value_rels
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
#
|
41
|
+
# Write the <richValueRels> element.
|
42
|
+
#
|
43
|
+
def write_rich_value_rels
|
44
|
+
xmlns = 'http://schemas.microsoft.com/office/spreadsheetml/2022/richvaluerel'
|
45
|
+
xmlns_r = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'
|
46
|
+
|
47
|
+
attributes = [
|
48
|
+
['xmlns', xmlns],
|
49
|
+
['xmlns:r', xmlns_r]
|
50
|
+
]
|
51
|
+
|
52
|
+
@writer.tag_elements('richValueRels', attributes) do
|
53
|
+
(0..(@value_count - 1)).each do |index|
|
54
|
+
# Write the rel element.
|
55
|
+
write_rel(index + 1)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
# Write the <rel> element.
|
62
|
+
#
|
63
|
+
def write_rel(id)
|
64
|
+
attributes = [['r:id', "rId#{id}"]]
|
65
|
+
|
66
|
+
@writer.empty_tag('rel', attributes)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'write_xlsx/package/xml_writer_simple'
|
5
|
+
require 'write_xlsx/utility'
|
6
|
+
|
7
|
+
module Writexlsx
|
8
|
+
module Package
|
9
|
+
#
|
10
|
+
# RichValueStructure - A class for writing the Excel XLSX rdrichvaluestructure.xml
|
11
|
+
# file.
|
12
|
+
#
|
13
|
+
# Used in conjunction with Excel::Writer::XLSX
|
14
|
+
#
|
15
|
+
# Copyright 2000-2024, John McNamara, jmcnamara@cpan.org
|
16
|
+
#
|
17
|
+
# Convert to Ruby by Hideo NAKAMURA, nakamura.hideo@gmail.com
|
18
|
+
#
|
19
|
+
class RichValueStructure
|
20
|
+
include Writexlsx::Utility
|
21
|
+
|
22
|
+
attr_writer :has_embedded_descriptions
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@writer = Package::XMLWriterSimple.new
|
26
|
+
@has_embedded_descriptions = false
|
27
|
+
end
|
28
|
+
|
29
|
+
def set_xml_writer(filename)
|
30
|
+
@writer.set_xml_writer(filename)
|
31
|
+
end
|
32
|
+
|
33
|
+
def assemble_xml_file
|
34
|
+
write_xml_declaration do
|
35
|
+
write_rv_structures
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
#
|
42
|
+
# Write the <rvStructures> element.
|
43
|
+
#
|
44
|
+
def write_rv_structures
|
45
|
+
xmlns = 'http://schemas.microsoft.com/office/spreadsheetml/2017/richdata'
|
46
|
+
|
47
|
+
attributes = [
|
48
|
+
['xmlns', xmlns],
|
49
|
+
['count', 1]
|
50
|
+
]
|
51
|
+
|
52
|
+
@writer.tag_elements('rvStructures', attributes) do
|
53
|
+
write_s
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
#
|
58
|
+
# Write the <s> element.
|
59
|
+
#
|
60
|
+
def write_s
|
61
|
+
attributes = [%w[t _localImage]]
|
62
|
+
|
63
|
+
@writer.tag_elements('s', attributes) do
|
64
|
+
write_k('_rvRel:LocalImageIdentifier', 'i')
|
65
|
+
write_k('CalcOrigin', 'i')
|
66
|
+
write_k('Text', 's') if @has_embedded_descriptions
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
#
|
71
|
+
# Write the <k> element.
|
72
|
+
#
|
73
|
+
def write_k(n, t)
|
74
|
+
attributes = [
|
75
|
+
['n', n],
|
76
|
+
['t', t]
|
77
|
+
]
|
78
|
+
|
79
|
+
@writer.empty_tag('k', attributes)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'write_xlsx/package/xml_writer_simple'
|
5
|
+
require 'write_xlsx/utility'
|
6
|
+
|
7
|
+
module Writexlsx
|
8
|
+
module Package
|
9
|
+
#
|
10
|
+
# RichValueTypes - A class for writing the Excel XLSX rdRichValueTypes.xml file.
|
11
|
+
#
|
12
|
+
# Used in conjunction with Excel::Writer::XLSX
|
13
|
+
#
|
14
|
+
# Copyright 2000-2024, John McNamara, jmcnamara@cpan.org
|
15
|
+
#
|
16
|
+
# Convert to Ruby by Hideo NAKAMURA, nakamura.hideo@gmail.com
|
17
|
+
#
|
18
|
+
class RichValueTypes
|
19
|
+
include Writexlsx::Utility
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
@writer = Package::XMLWriterSimple.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def set_xml_writer(filename)
|
26
|
+
@writer.set_xml_writer(filename)
|
27
|
+
end
|
28
|
+
|
29
|
+
def assemble_xml_file
|
30
|
+
write_xml_declaration do
|
31
|
+
write_rv_types_info
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
#
|
38
|
+
# Write the <rvTypesInfo> element.
|
39
|
+
#
|
40
|
+
def write_rv_types_info
|
41
|
+
xmlns = 'http://schemas.microsoft.com/office/spreadsheetml/2017/richdata2'
|
42
|
+
xmlns_mc = 'http://schemas.openxmlformats.org/markup-compatibility/2006'
|
43
|
+
xmlns_x = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'
|
44
|
+
|
45
|
+
attributes = [
|
46
|
+
['xmlns', xmlns],
|
47
|
+
['xmlns:mc', xmlns_mc],
|
48
|
+
['mc:Ignorable', 'x'],
|
49
|
+
['xmlns:x', xmlns_x]
|
50
|
+
]
|
51
|
+
|
52
|
+
key_flags = [
|
53
|
+
['_Self', %w[ExcludeFromFile ExcludeFromCalcComparison]],
|
54
|
+
['_DisplayString', ['ExcludeFromCalcComparison']],
|
55
|
+
['_Flags', ['ExcludeFromCalcComparison']],
|
56
|
+
['_Format', ['ExcludeFromCalcComparison']],
|
57
|
+
['_SubLabel', ['ExcludeFromCalcComparison']],
|
58
|
+
['_Attribution', ['ExcludeFromCalcComparison']],
|
59
|
+
['_Icon', ['ExcludeFromCalcComparison']],
|
60
|
+
['_Display', ['ExcludeFromCalcComparison']],
|
61
|
+
['_CanonicalPropertyNames', ['ExcludeFromCalcComparison']],
|
62
|
+
['_ClassificationId', ['ExcludeFromCalcComparison']]
|
63
|
+
]
|
64
|
+
|
65
|
+
@writer.tag_elements('rvTypesInfo', attributes) do
|
66
|
+
@writer.tag_elements('global') do
|
67
|
+
@writer.tag_elements('keyFlags') do
|
68
|
+
# Write the keyFlags element.
|
69
|
+
key_flags.each do |key_flag|
|
70
|
+
write_key(key_flag[0], key_flag[1])
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
#
|
78
|
+
# Write the <key> element.
|
79
|
+
#
|
80
|
+
def write_key(name, flags = [])
|
81
|
+
attributes = [['name', name]]
|
82
|
+
|
83
|
+
@writer.tag_elements('key', attributes) do
|
84
|
+
flags.each do |flag|
|
85
|
+
write_flag(flag)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
#
|
91
|
+
# Write the <flag> element.
|
92
|
+
#
|
93
|
+
def write_flag(name)
|
94
|
+
attributes = [
|
95
|
+
['name', name],
|
96
|
+
['value', 1]
|
97
|
+
]
|
98
|
+
|
99
|
+
@writer.empty_tag('flag', attributes)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
data/lib/write_xlsx/utility.rb
CHANGED
@@ -988,6 +988,178 @@ module Writexlsx
|
|
988
988
|
def write_a_end_para_rpr # :nodoc:
|
989
989
|
@writer.empty_tag('a:endParaRPr', [%w[lang en-US]])
|
990
990
|
end
|
991
|
+
|
992
|
+
#
|
993
|
+
# Extract information from the image file such as dimension, type, filename,
|
994
|
+
# and extension. Also keep track of previously seen images to optimise out
|
995
|
+
# any duplicates.
|
996
|
+
#
|
997
|
+
def get_image_properties(filename)
|
998
|
+
# Note the image_id, and previous_images mechanism isn't currently used.
|
999
|
+
x_dpi = 96
|
1000
|
+
y_dpi = 96
|
1001
|
+
|
1002
|
+
workbook = @workbook || self
|
1003
|
+
|
1004
|
+
# Open the image file and import the data.
|
1005
|
+
data = File.binread(filename)
|
1006
|
+
md5 = Digest::MD5.hexdigest(data)
|
1007
|
+
if data.unpack1('x A3') == 'PNG'
|
1008
|
+
# Test for PNGs.
|
1009
|
+
type, width, height, x_dpi, y_dpi = process_png(data)
|
1010
|
+
workbook.image_types[:png] = 1
|
1011
|
+
elsif data.unpack1('n') == 0xFFD8
|
1012
|
+
# Test for JPEG files.
|
1013
|
+
type, width, height, x_dpi, y_dpi = process_jpg(data, filename)
|
1014
|
+
workbook.image_types[:jpeg] = 1
|
1015
|
+
elsif data.unpack1('A4') == 'GIF8'
|
1016
|
+
# Test for GIFs.
|
1017
|
+
type, width, height, x_dpi, y_dpi = process_gif(data, filename)
|
1018
|
+
workbook.image_types[:gif] = 1
|
1019
|
+
elsif data.unpack1('A2') == 'BM'
|
1020
|
+
# Test for BMPs.
|
1021
|
+
type, width, height = process_bmp(data, filename)
|
1022
|
+
workbook.image_types[:bmp] = 1
|
1023
|
+
else
|
1024
|
+
# TODO. Add Image::Size to support other types.
|
1025
|
+
raise "Unsupported image format for file: #{filename}\n"
|
1026
|
+
end
|
1027
|
+
|
1028
|
+
# Set a default dpi for images with 0 dpi.
|
1029
|
+
x_dpi = 96 if x_dpi == 0
|
1030
|
+
y_dpi = 96 if y_dpi == 0
|
1031
|
+
|
1032
|
+
[type, width, height, File.basename(filename), x_dpi, y_dpi, md5]
|
1033
|
+
end
|
1034
|
+
|
1035
|
+
#
|
1036
|
+
# Extract width and height information from a PNG file.
|
1037
|
+
#
|
1038
|
+
def process_png(data)
|
1039
|
+
type = 'png'
|
1040
|
+
width = 0
|
1041
|
+
height = 0
|
1042
|
+
x_dpi = 96
|
1043
|
+
y_dpi = 96
|
1044
|
+
|
1045
|
+
offset = 8
|
1046
|
+
data_length = data.size
|
1047
|
+
|
1048
|
+
# Search through the image data to read the height and width in th the
|
1049
|
+
# IHDR element. Also read the DPI in the pHYs element.
|
1050
|
+
while offset < data_length
|
1051
|
+
|
1052
|
+
length = data[offset + 0, 4].unpack1("N")
|
1053
|
+
png_type = data[offset + 4, 4].unpack1("A4")
|
1054
|
+
|
1055
|
+
case png_type
|
1056
|
+
when "IHDR"
|
1057
|
+
width = data[offset + 8, 4].unpack1("N")
|
1058
|
+
height = data[offset + 12, 4].unpack1("N")
|
1059
|
+
when "pHYs"
|
1060
|
+
x_ppu = data[offset + 8, 4].unpack1("N")
|
1061
|
+
y_ppu = data[offset + 12, 4].unpack1("N")
|
1062
|
+
units = data[offset + 16, 1].unpack1("C")
|
1063
|
+
|
1064
|
+
if units == 1
|
1065
|
+
x_dpi = x_ppu * 0.0254
|
1066
|
+
y_dpi = y_ppu * 0.0254
|
1067
|
+
end
|
1068
|
+
end
|
1069
|
+
|
1070
|
+
offset = offset + length + 12
|
1071
|
+
|
1072
|
+
break if png_type == "IEND"
|
1073
|
+
end
|
1074
|
+
raise "#{filename}: no size data found in png image.\n" unless height
|
1075
|
+
|
1076
|
+
[type, width, height, x_dpi, y_dpi]
|
1077
|
+
end
|
1078
|
+
|
1079
|
+
def process_jpg(data, filename)
|
1080
|
+
type = 'jpeg'
|
1081
|
+
x_dpi = 96
|
1082
|
+
y_dpi = 96
|
1083
|
+
|
1084
|
+
offset = 2
|
1085
|
+
data_length = data.bytesize
|
1086
|
+
|
1087
|
+
# Search through the image data to read the JPEG markers.
|
1088
|
+
while offset < data_length
|
1089
|
+
marker = data[offset + 0, 2].unpack1("n")
|
1090
|
+
length = data[offset + 2, 2].unpack1("n")
|
1091
|
+
|
1092
|
+
# Read the height and width in the 0xFFCn elements
|
1093
|
+
# (Except C4, C8 and CC which aren't SOF markers).
|
1094
|
+
if (marker & 0xFFF0) == 0xFFC0 &&
|
1095
|
+
marker != 0xFFC4 && marker != 0xFFCC
|
1096
|
+
height = data[offset + 5, 2].unpack1("n")
|
1097
|
+
width = data[offset + 7, 2].unpack1("n")
|
1098
|
+
end
|
1099
|
+
|
1100
|
+
# Read the DPI in the 0xFFE0 element.
|
1101
|
+
if marker == 0xFFE0
|
1102
|
+
units = data[offset + 11, 1].unpack1("C")
|
1103
|
+
x_density = data[offset + 12, 2].unpack1("n")
|
1104
|
+
y_density = data[offset + 14, 2].unpack1("n")
|
1105
|
+
|
1106
|
+
if units == 1
|
1107
|
+
x_dpi = x_density
|
1108
|
+
y_dpi = y_density
|
1109
|
+
elsif units == 2
|
1110
|
+
x_dpi = x_density * 2.54
|
1111
|
+
y_dpi = y_density * 2.54
|
1112
|
+
end
|
1113
|
+
end
|
1114
|
+
|
1115
|
+
offset += length + 2
|
1116
|
+
break if marker == 0xFFDA
|
1117
|
+
end
|
1118
|
+
|
1119
|
+
raise "#{filename}: no size data found in jpeg image.\n" unless height
|
1120
|
+
|
1121
|
+
[type, width, height, x_dpi, y_dpi]
|
1122
|
+
end
|
1123
|
+
|
1124
|
+
#
|
1125
|
+
# Extract width and height information from a GIF file.
|
1126
|
+
#
|
1127
|
+
def process_gif(data, filename)
|
1128
|
+
type = 'gif'
|
1129
|
+
x_dpi = 96
|
1130
|
+
y_dpi = 96
|
1131
|
+
|
1132
|
+
width = data[6, 2].unpack1("v")
|
1133
|
+
height = data[8, 2].unpack1("v")
|
1134
|
+
|
1135
|
+
raise "#{filename}: no size data found in gif image.\n" if height.nil?
|
1136
|
+
|
1137
|
+
[type, width, height, x_dpi, y_dpi]
|
1138
|
+
end
|
1139
|
+
|
1140
|
+
# Extract width and height information from a BMP file.
|
1141
|
+
def process_bmp(data, filename) # :nodoc:
|
1142
|
+
type = 'bmp'
|
1143
|
+
|
1144
|
+
# Check that the file is big enough to be a bitmap.
|
1145
|
+
raise "#{filename} doesn't contain enough data." if data.bytesize <= 0x36
|
1146
|
+
|
1147
|
+
# Read the bitmap width and height. Verify the sizes.
|
1148
|
+
width, height = data.unpack("x18 V2")
|
1149
|
+
raise "#{filename}: largest image width #{width} supported is 65k." if width > 0xFFFF
|
1150
|
+
raise "#{filename}: largest image height supported is 65k." if height > 0xFFFF
|
1151
|
+
|
1152
|
+
# Read the bitmap planes and bpp data. Verify them.
|
1153
|
+
planes, bitcount = data.unpack("x26 v2")
|
1154
|
+
raise "#{filename} isn't a 24bit true color bitmap." unless bitcount == 24
|
1155
|
+
raise "#{filename}: only 1 plane supported in bitmap image." unless planes == 1
|
1156
|
+
|
1157
|
+
# Read the bitmap compression. Verify compression.
|
1158
|
+
compression = data.unpack1("x30 V")
|
1159
|
+
raise "#{filename}: compression not supported in bitmap image." unless compression == 0
|
1160
|
+
|
1161
|
+
[type, width, height]
|
1162
|
+
end
|
991
1163
|
end
|
992
1164
|
|
993
1165
|
module WriteDPtPoint
|
data/lib/write_xlsx/version.rb
CHANGED