caxlsx 4.3.0 → 4.4.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/CHANGELOG.md +9 -0
- data/README.md +11 -8
- data/lib/axlsx/package.rb +40 -8
- data/lib/axlsx/stylesheet/styles.rb +1 -0
- data/lib/axlsx/stylesheet/theme.rb +163 -0
- data/lib/axlsx/util/constants.rb +12 -0
- data/lib/axlsx/util/mime_type_utils.rb +72 -13
- data/lib/axlsx/util/uri_utils.rb +70 -0
- data/lib/axlsx/util/validators.rb +4 -4
- data/lib/axlsx/version.rb +1 -1
- data/lib/axlsx/workbook/workbook.rb +7 -0
- data/lib/axlsx/workbook/workbook_view.rb +1 -1
- data/lib/axlsx.rb +2 -0
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 97d26d1c2165080987473b430781c434b27f62510950ce0c27792058ab493bbf
|
4
|
+
data.tar.gz: 6d9f1f55b9b6602d86e2f6e30ce5e0589ac811bf49f8991f7dbc7f8fec42e6e3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fd8f19deac0273ac8f59aca129190fc395b23077e34bfaafaa50dc48b9e6565430b94c7bbac8f19fd7272edacf0cac04438a2dc464b22670a575be75259cb2fe
|
7
|
+
data.tar.gz: 92ef32593a1ba1132b2b53ae08ecd82eb7e538edfdbb5e764fb325c57f6a2c66b56de6b67e04a48aa3dde5b663468a06d876292103f22fcf456b0da6563df967
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,15 @@ CHANGELOG
|
|
2
2
|
---------
|
3
3
|
- **Unreleased**
|
4
4
|
|
5
|
+
- **September.01.25**: 4.4.0
|
6
|
+
- [PR #477](https://github.com/caxlsx/caxlsx/pull/477) Add package-level encryption and password protection.
|
7
|
+
- [PR #476](https://github.com/caxlsx/caxlsx/pull/476) Add Excel for Windows integration testing.
|
8
|
+
- [PR #476](https://github.com/caxlsx/caxlsx/pull/476) Add Excel for MacOS integration testing.
|
9
|
+
- [PR #469](https://github.com/caxlsx/caxlsx/pull/469) Add default theme file to Excel package.
|
10
|
+
- [PR #475](https://github.com/caxlsx/caxlsx/pull/475) Use timecop to fix transient time failure in tests
|
11
|
+
- [PR #474](https://github.com/caxlsx/caxlsx/pull/474) Add Windows and MacOS to the CI.
|
12
|
+
- [PR #474](https://github.com/caxlsx/caxlsx/pull/474) Fix local image file MIME type detection on Windows.
|
13
|
+
- [PR #474](https://github.com/caxlsx/caxlsx/pull/474) Load only HTTP headers when determining remote file MIME type.
|
5
14
|
|
6
15
|
- **August.16.25**: 4.3.0
|
7
16
|
- [PR #421](https://github.com/caxlsx/caxlsx/pull/421) Add Rubyzip >= 2.4 support
|
data/README.md
CHANGED
@@ -51,20 +51,21 @@ cell level input data validation.
|
|
51
51
|
|
52
52
|
15. Support for page margins and print options
|
53
53
|
|
54
|
-
16. Support for
|
54
|
+
16. Support for workbook-level encryption and password protection (requires [ooxml_crypt](https://github.com/teamsimplepay/ooxml_crypt) gem which only supports MRI Ruby.)
|
55
55
|
|
56
|
-
17.
|
57
|
-
and Numbers
|
56
|
+
17. Support for sheet-level password and non-password protection.
|
58
57
|
|
59
|
-
18.
|
58
|
+
18. First stage interoperability support for GoogleDocs, LibreOffice, and Numbers.
|
60
59
|
|
61
|
-
19.
|
60
|
+
19. Support for defined names, which gives you repeated header rows for printing.
|
62
61
|
|
63
|
-
20.
|
62
|
+
20. Data labels for charts as well as series color customization.
|
64
63
|
|
65
|
-
21.
|
64
|
+
21. Support for sheet headers and footers
|
66
65
|
|
67
|
-
22.
|
66
|
+
22. Pivot Tables
|
67
|
+
|
68
|
+
23. Page Breaks
|
68
69
|
|
69
70
|
|
70
71
|
## Install
|
@@ -127,6 +128,8 @@ Currently the following additional gems are available:
|
|
127
128
|
* Provides a `.axlsx` renderer to Rails so you can move all your spreadsheet code from your controller into view files.
|
128
129
|
- [activeadmin-caxlsx](https://github.com/caxlsx/activeadmin-caxlsx)
|
129
130
|
* An Active Admin plugin that includes DSL to create downloadable reports.
|
131
|
+
- [ooxml_crypt](https://github.com/teamsimplepay/ooxml_crypt)
|
132
|
+
* Required to enable workbook encryption and password protection.
|
130
133
|
|
131
134
|
## Security
|
132
135
|
|
data/lib/axlsx/package.rb
CHANGED
@@ -82,6 +82,8 @@ module Axlsx
|
|
82
82
|
# @option options [String] :zip_command When `nil`, `#serialize` with RubyZip to
|
83
83
|
# zip the XLSX file contents. When a String, the provided zip command (e.g.,
|
84
84
|
# "zip") is used to zip the file contents (may be faster for large files)
|
85
|
+
# @option options [String] :password When specified, the serialized packaged will be
|
86
|
+
# encrypted with the password. Requires ooxml_crypt gem.
|
85
87
|
# @return [Boolean] False if confirm_valid and validation errors exist. True if the package was serialized
|
86
88
|
# @note A tremendous amount of effort has gone into ensuring that you cannot create invalid xlsx documents.
|
87
89
|
# options[:confirm_valid] should be used in the rare case that you cannot open the serialized file.
|
@@ -108,7 +110,7 @@ module Axlsx
|
|
108
110
|
workbook.apply_styles
|
109
111
|
end
|
110
112
|
|
111
|
-
confirm_valid, zip_command = parse_serialize_options(options, secondary_options)
|
113
|
+
confirm_valid, zip_command, password = parse_serialize_options(options, secondary_options)
|
112
114
|
return false unless !confirm_valid || validate.empty?
|
113
115
|
|
114
116
|
zip_provider = if zip_command
|
@@ -120,15 +122,31 @@ module Axlsx
|
|
120
122
|
zip_provider.open(output) do |zip|
|
121
123
|
write_parts(zip)
|
122
124
|
end
|
125
|
+
|
126
|
+
if password && !password.empty?
|
127
|
+
require_ooxml_crypt!
|
128
|
+
OoxmlCrypt.encrypt_file(output, password, output)
|
129
|
+
end
|
130
|
+
|
123
131
|
true
|
124
132
|
ensure
|
125
133
|
Relationship.clear_ids_cache
|
126
134
|
end
|
127
135
|
|
128
136
|
# Serialize your workbook to a StringIO instance
|
129
|
-
# @param [Boolean]
|
130
|
-
#
|
131
|
-
|
137
|
+
# @param [Boolean] old_confirm_valid (Deprecated) Validate the package prior to serialization.
|
138
|
+
# Use :confirm_valid keyword arg instead.
|
139
|
+
# @option kwargs [Boolean] :confirm_valid Validate the package prior to serialization.
|
140
|
+
# @option kwargs [String] :password When specified, the serialized packaged will be
|
141
|
+
# encrypted with the password. Requires ooxml_crypt gem.
|
142
|
+
# @return [StringIO|Boolean] False if confirm_valid and validation errors exist. Rewound string IO if not.
|
143
|
+
def to_stream(old_confirm_valid = nil, confirm_valid: false, password: nil)
|
144
|
+
unless old_confirm_valid.nil?
|
145
|
+
warn "[DEPRECATION] Axlsx::Package#to_stream with confirm_valid as a non-keyword arg is deprecated. " \
|
146
|
+
"Use keyword arg instead e.g., package.to_stream(confirm_valid: false)"
|
147
|
+
confirm_valid ||= old_confirm_valid
|
148
|
+
end
|
149
|
+
|
132
150
|
unless workbook.styles_applied
|
133
151
|
workbook.apply_styles
|
134
152
|
end
|
@@ -140,6 +158,12 @@ module Axlsx
|
|
140
158
|
write_parts(zip)
|
141
159
|
end
|
142
160
|
stream.rewind
|
161
|
+
|
162
|
+
if password && !password.empty?
|
163
|
+
require_ooxml_crypt!
|
164
|
+
stream = StringIO.new(OoxmlCrypt.encrypt(stream.read, password))
|
165
|
+
end
|
166
|
+
|
143
167
|
stream
|
144
168
|
ensure
|
145
169
|
Relationship.clear_ids_cache
|
@@ -221,6 +245,7 @@ module Axlsx
|
|
221
245
|
def parts
|
222
246
|
parts = [
|
223
247
|
{ entry: "xl/#{STYLES_PN}", doc: workbook.styles, schema: SML_XSD },
|
248
|
+
{ entry: "xl/#{THEME_PN}", doc: workbook.theme, schema: THEME_XSD },
|
224
249
|
{ entry: CORE_PN, doc: @core, schema: CORE_XSD },
|
225
250
|
{ entry: APP_PN, doc: @app, schema: APP_XSD },
|
226
251
|
{ entry: WORKBOOK_RELS_PN, doc: workbook.relationships, schema: RELS_XSD },
|
@@ -357,6 +382,7 @@ module Axlsx
|
|
357
382
|
c_types << Override.new(PartName: "/#{APP_PN}", ContentType: APP_CT)
|
358
383
|
c_types << Override.new(PartName: "/#{CORE_PN}", ContentType: CORE_CT)
|
359
384
|
c_types << Override.new(PartName: "/xl/#{STYLES_PN}", ContentType: STYLES_CT)
|
385
|
+
c_types << Override.new(PartName: "/xl/#{THEME_PN}", ContentType: THEME_CT)
|
360
386
|
c_types << Axlsx::Override.new(PartName: "/#{WORKBOOK_PN}", ContentType: WORKBOOK_CT)
|
361
387
|
c_types.lock
|
362
388
|
c_types
|
@@ -375,8 +401,8 @@ module Axlsx
|
|
375
401
|
end
|
376
402
|
|
377
403
|
# Parse the arguments of `#serialize`
|
378
|
-
# @return [Boolean, (String or nil)] Returns
|
379
|
-
# `confirm_valid` and
|
404
|
+
# @return [Boolean, (String or nil), (String or nil)] Returns a 3-tuple where values are
|
405
|
+
# `confirm_valid`, `zip_command`, and `password`.
|
380
406
|
# @private
|
381
407
|
def parse_serialize_options(options, secondary_options)
|
382
408
|
if secondary_options
|
@@ -385,17 +411,23 @@ module Axlsx
|
|
385
411
|
end
|
386
412
|
if options.is_a?(Hash)
|
387
413
|
options.merge!(secondary_options || {})
|
388
|
-
invalid_keys = options.keys - [:confirm_valid, :zip_command]
|
414
|
+
invalid_keys = options.keys - [:confirm_valid, :zip_command, :password]
|
389
415
|
if invalid_keys.any?
|
390
416
|
raise ArgumentError, "Invalid keyword arguments: #{invalid_keys}"
|
391
417
|
end
|
392
418
|
|
393
|
-
[options.fetch(:confirm_valid, false), options.fetch(:zip_command, nil)]
|
419
|
+
[options.fetch(:confirm_valid, false), options.fetch(:zip_command, nil), options.fetch(:password, nil)]
|
394
420
|
else
|
395
421
|
warn "[DEPRECATION] Axlsx::Package#serialize with confirm_valid as a boolean is deprecated. " \
|
396
422
|
"Use keyword args instead e.g., package.serialize(output, confirm_valid: false)"
|
397
423
|
parse_serialize_options((secondary_options || {}).merge(confirm_valid: options), nil)
|
398
424
|
end
|
399
425
|
end
|
426
|
+
|
427
|
+
def require_ooxml_crypt!
|
428
|
+
return if defined?(OoxmlCrypt)
|
429
|
+
|
430
|
+
raise 'Axlsx encryption requires ooxml_crypt gem'
|
431
|
+
end
|
400
432
|
end
|
401
433
|
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Axlsx
|
4
|
+
# Theme represents the theme part of the package and is responsible for
|
5
|
+
# generating the default Office theme that is required for encryption compatibility
|
6
|
+
class Theme
|
7
|
+
# The part name of this theme
|
8
|
+
# @return [String]
|
9
|
+
def pn
|
10
|
+
THEME_PN
|
11
|
+
end
|
12
|
+
|
13
|
+
# Serializes the default theme to XML
|
14
|
+
# @param [String] str
|
15
|
+
# @return [String]
|
16
|
+
def to_xml_string(str = +'')
|
17
|
+
str << <<~XML.delete("\n")
|
18
|
+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
19
|
+
<a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="Office Theme">
|
20
|
+
<a:themeElements>
|
21
|
+
<a:clrScheme name="Office">
|
22
|
+
<a:dk1><a:sysClr val="windowText" lastClr="000000"/></a:dk1>
|
23
|
+
<a:lt1><a:sysClr val="window" lastClr="FFFFFF"/></a:lt1>
|
24
|
+
<a:dk2><a:srgbClr val="1F497D"/></a:dk2>
|
25
|
+
<a:lt2><a:srgbClr val="EEECE1"/></a:lt2>
|
26
|
+
<a:accent1><a:srgbClr val="4F81BD"/></a:accent1>
|
27
|
+
<a:accent2><a:srgbClr val="C0504D"/></a:accent2>
|
28
|
+
<a:accent3><a:srgbClr val="9BBB59"/></a:accent3>
|
29
|
+
<a:accent4><a:srgbClr val="8064A2"/></a:accent4>
|
30
|
+
<a:accent5><a:srgbClr val="4BACC6"/></a:accent5>
|
31
|
+
<a:accent6><a:srgbClr val="F79646"/></a:accent6>
|
32
|
+
<a:hlink><a:srgbClr val="0000FF"/></a:hlink>
|
33
|
+
<a:folHlink><a:srgbClr val="800080"/></a:folHlink>
|
34
|
+
</a:clrScheme>
|
35
|
+
|
36
|
+
<a:fontScheme name="Office">
|
37
|
+
<a:majorFont>
|
38
|
+
<a:latin typeface="Cambria"/>
|
39
|
+
<a:ea typeface=""/>
|
40
|
+
<a:cs typeface=""/>
|
41
|
+
</a:majorFont>
|
42
|
+
<a:minorFont>
|
43
|
+
<a:latin typeface="Calibri"/>
|
44
|
+
<a:ea typeface=""/>
|
45
|
+
<a:cs typeface=""/>
|
46
|
+
</a:minorFont>
|
47
|
+
</a:fontScheme>
|
48
|
+
|
49
|
+
<a:fmtScheme name="Office">
|
50
|
+
<a:fillStyleLst>
|
51
|
+
<a:solidFill><a:schemeClr val="phClr"/></a:solidFill>
|
52
|
+
<a:gradFill rotWithShape="1">
|
53
|
+
<a:gsLst>
|
54
|
+
<a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="50000"/><a:satMod val="300000"/></a:schemeClr></a:gs>
|
55
|
+
<a:gs pos="35000"><a:schemeClr val="phClr"><a:tint val="37000"/><a:satMod val="300000"/></a:schemeClr></a:gs>
|
56
|
+
<a:gs pos="100000"><a:schemeClr val="phClr"><a:tint val="15000"/><a:satMod val="350000"/></a:schemeClr></a:gs>
|
57
|
+
</a:gsLst>
|
58
|
+
<a:lin ang="16200000" scaled="1"/>
|
59
|
+
</a:gradFill>
|
60
|
+
<a:gradFill rotWithShape="1">
|
61
|
+
<a:gsLst>
|
62
|
+
<a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="100000"/><a:shade val="100000"/><a:satMod val="130000"/></a:schemeClr></a:gs>
|
63
|
+
<a:gs pos="100000"><a:schemeClr val="phClr"><a:tint val="50000"/><a:shade val="100000"/><a:satMod val="350000"/></a:schemeClr></a:gs>
|
64
|
+
</a:gsLst>
|
65
|
+
<a:lin ang="16200000" scaled="false"/>
|
66
|
+
</a:gradFill>
|
67
|
+
</a:fillStyleLst>
|
68
|
+
|
69
|
+
<a:lnStyleLst>
|
70
|
+
<a:ln w="9525" cap="flat" cmpd="sng" algn="ctr">
|
71
|
+
<a:solidFill><a:schemeClr val="phClr"><a:shade val="95000"/><a:satMod val="105000"/></a:schemeClr></a:solidFill>
|
72
|
+
<a:prstDash val="solid"/>
|
73
|
+
</a:ln>
|
74
|
+
<a:ln w="25400" cap="flat" cmpd="sng" algn="ctr">
|
75
|
+
<a:solidFill><a:schemeClr val="phClr"/></a:solidFill>
|
76
|
+
<a:prstDash val="solid"/>
|
77
|
+
</a:ln>
|
78
|
+
<a:ln w="38100" cap="flat" cmpd="sng" algn="ctr">
|
79
|
+
<a:solidFill><a:schemeClr val="phClr"/></a:solidFill>
|
80
|
+
<a:prstDash val="solid"/>
|
81
|
+
</a:ln>
|
82
|
+
</a:lnStyleLst>
|
83
|
+
|
84
|
+
<a:effectStyleLst>
|
85
|
+
<a:effectStyle>
|
86
|
+
<a:effectLst>
|
87
|
+
<a:outerShdw blurRad="40000" dist="20000" dir="5400000" rotWithShape="false">
|
88
|
+
<a:srgbClr val="000000"><a:alpha val="38000"/></a:srgbClr>
|
89
|
+
</a:outerShdw>
|
90
|
+
</a:effectLst>
|
91
|
+
</a:effectStyle>
|
92
|
+
<a:effectStyle>
|
93
|
+
<a:effectLst>
|
94
|
+
<a:outerShdw blurRad="40000" dist="23000" dir="5400000" rotWithShape="false">
|
95
|
+
<a:srgbClr val="000000"><a:alpha val="35000"/></a:srgbClr>
|
96
|
+
</a:outerShdw>
|
97
|
+
</a:effectLst>
|
98
|
+
</a:effectStyle>
|
99
|
+
<a:effectStyle>
|
100
|
+
<a:effectLst>
|
101
|
+
<a:outerShdw blurRad="40000" dist="23000" dir="5400000" rotWithShape="false">
|
102
|
+
<a:srgbClr val="000000"><a:alpha val="35000"/></a:srgbClr>
|
103
|
+
</a:outerShdw>
|
104
|
+
</a:effectLst>
|
105
|
+
<a:scene3d>
|
106
|
+
<a:camera prst="orthographicFront"><a:rot lat="0" lon="0" rev="0"/></a:camera>
|
107
|
+
<a:lightRig rig="threePt" dir="t"><a:rot lat="0" lon="0" rev="1200000"/></a:lightRig>
|
108
|
+
</a:scene3d>
|
109
|
+
<a:sp3d><a:bevelT w="63500" h="25400"/></a:sp3d>
|
110
|
+
</a:effectStyle>
|
111
|
+
</a:effectStyleLst>
|
112
|
+
|
113
|
+
<a:bgFillStyleLst>
|
114
|
+
<a:solidFill><a:schemeClr val="phClr"/></a:solidFill>
|
115
|
+
<a:gradFill rotWithShape="1">
|
116
|
+
<a:gsLst>
|
117
|
+
<a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="40000"/><a:satMod val="350000"/></a:schemeClr></a:gs>
|
118
|
+
<a:gs pos="40000"><a:schemeClr val="phClr"><a:tint val="45000"/><a:shade val="99000"/><a:satMod val="350000"/></a:schemeClr></a:gs>
|
119
|
+
<a:gs pos="100000"><a:schemeClr val="phClr"><a:shade val="20000"/><a:satMod val="255000"/></a:schemeClr></a:gs>
|
120
|
+
</a:gsLst>
|
121
|
+
<a:path path="circle"><a:fillToRect l="50000" t="-80000" r="50000" b="180000"/></a:path>
|
122
|
+
</a:gradFill>
|
123
|
+
<a:gradFill rotWithShape="1">
|
124
|
+
<a:gsLst>
|
125
|
+
<a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="80000"/><a:satMod val="300000"/></a:schemeClr></a:gs>
|
126
|
+
<a:gs pos="100000"><a:schemeClr val="phClr"><a:shade val="30000"/><a:satMod val="200000"/></a:schemeClr></a:gs>
|
127
|
+
</a:gsLst>
|
128
|
+
<a:path path="circle"><a:fillToRect l="50000" t="50000" r="50000" b="50000"/></a:path>
|
129
|
+
</a:gradFill>
|
130
|
+
</a:bgFillStyleLst>
|
131
|
+
</a:fmtScheme>
|
132
|
+
</a:themeElements>
|
133
|
+
|
134
|
+
<a:objectDefaults>
|
135
|
+
<a:spDef>
|
136
|
+
<a:spPr/>
|
137
|
+
<a:bodyPr/>
|
138
|
+
<a:lstStyle/>
|
139
|
+
<a:style>
|
140
|
+
<a:lnRef idx="1"><a:schemeClr val="accent1"/></a:lnRef>
|
141
|
+
<a:fillRef idx="3"><a:schemeClr val="accent1"/></a:fillRef>
|
142
|
+
<a:effectRef idx="2"><a:schemeClr val="accent1"/></a:effectRef>
|
143
|
+
<a:fontRef idx="minor"><a:schemeClr val="lt1"/></a:fontRef>
|
144
|
+
</a:style>
|
145
|
+
</a:spDef>
|
146
|
+
<a:lnDef>
|
147
|
+
<a:spPr/>
|
148
|
+
<a:bodyPr/>
|
149
|
+
<a:lstStyle/>
|
150
|
+
<a:style>
|
151
|
+
<a:lnRef idx="2"><a:schemeClr val="accent1"/></a:lnRef>
|
152
|
+
<a:fillRef idx="0"><a:schemeClr val="accent1"/></a:fillRef>
|
153
|
+
<a:effectRef idx="1"><a:schemeClr val="accent1"/></a:effectRef>
|
154
|
+
<a:fontRef idx="minor"><a:schemeClr val="tx1"/></a:fontRef>
|
155
|
+
</a:style>
|
156
|
+
</a:lnDef>
|
157
|
+
</a:objectDefaults>
|
158
|
+
<a:extraClrSchemeLst/>
|
159
|
+
</a:theme>
|
160
|
+
XML
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
data/lib/axlsx/util/constants.rb
CHANGED
@@ -79,6 +79,9 @@ module Axlsx
|
|
79
79
|
# shared strings namespace
|
80
80
|
SHARED_STRINGS_R = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings"
|
81
81
|
|
82
|
+
# theme rels namespace
|
83
|
+
THEME_R = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme"
|
84
|
+
|
82
85
|
# drawing rels namespace
|
83
86
|
DRAWING_R = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing"
|
84
87
|
|
@@ -133,6 +136,9 @@ module Axlsx
|
|
133
136
|
# shared strings content type
|
134
137
|
SHARED_STRINGS_CT = "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"
|
135
138
|
|
139
|
+
# theme content type
|
140
|
+
THEME_CT = "application/vnd.openxmlformats-officedocument.theme+xml"
|
141
|
+
|
136
142
|
# core content type
|
137
143
|
CORE_CT = "application/vnd.openxmlformats-package.core-properties+xml"
|
138
144
|
|
@@ -187,6 +193,9 @@ module Axlsx
|
|
187
193
|
# shared_strings part
|
188
194
|
SHARED_STRINGS_PN = "sharedStrings.xml"
|
189
195
|
|
196
|
+
# theme part
|
197
|
+
THEME_PN = "theme/theme1.xml"
|
198
|
+
|
190
199
|
# app part
|
191
200
|
APP_PN = "docProps/app.xml"
|
192
201
|
|
@@ -259,6 +268,9 @@ module Axlsx
|
|
259
268
|
# drawing validation schema
|
260
269
|
DRAWING_XSD = "#{SCHEMA_BASE}dml-spreadsheetDrawing.xsd"
|
261
270
|
|
271
|
+
# theme validation schema
|
272
|
+
THEME_XSD = "#{SCHEMA_BASE}dml-main.xsd"
|
273
|
+
|
262
274
|
# number format id for percentage formatting using the default formatting id.
|
263
275
|
NUM_FMT_PERCENT = 9
|
264
276
|
|
@@ -1,22 +1,81 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'open-uri'
|
4
|
-
|
5
3
|
module Axlsx
|
6
4
|
# This module defines some utils related with mime type detection
|
7
5
|
module MimeTypeUtils
|
8
|
-
#
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
6
|
+
# Extension to MIME type mapping for Windows fallback
|
7
|
+
EXTENSION_FALLBACKS = {
|
8
|
+
'.jpg' => 'image/jpeg',
|
9
|
+
'.jpeg' => 'image/jpeg',
|
10
|
+
'.png' => 'image/png',
|
11
|
+
'.gif' => 'image/gif'
|
12
|
+
}.freeze
|
13
|
+
|
14
|
+
class << self
|
15
|
+
# Detect a file mime type
|
16
|
+
# @param [String] v File path
|
17
|
+
# @return [String] File mime type
|
18
|
+
def get_mime_type(v)
|
19
|
+
mime_type = Marcel::MimeType.for(Pathname.new(v))
|
20
|
+
|
21
|
+
# Windows fallback: Marcel sometimes returns application/octet-stream for valid image files
|
22
|
+
if mime_type == 'application/octet-stream' && windows_platform?
|
23
|
+
extension = File.extname(v).downcase
|
24
|
+
# Verify it's actually an image by checking the file header
|
25
|
+
if EXTENSION_FALLBACKS.key?(extension) && File.exist?(v) && valid_image_file?(v, extension)
|
26
|
+
mime_type = EXTENSION_FALLBACKS[extension]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
mime_type
|
31
|
+
end
|
32
|
+
|
33
|
+
# Detect a file mime type from URI
|
34
|
+
# @param [String] v URI
|
35
|
+
# @return [String] File mime type
|
36
|
+
def get_mime_type_from_uri(v)
|
37
|
+
uri = URI.parse(v)
|
38
|
+
|
39
|
+
unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
|
40
|
+
raise URI::InvalidURIError, "Only HTTP/HTTPS URIs are supported"
|
41
|
+
end
|
42
|
+
|
43
|
+
response = UriUtils.fetch_headers(uri)
|
44
|
+
mime_type = response&.[]('content-type')&.split(';')&.first&.strip
|
45
|
+
|
46
|
+
if mime_type.nil? || mime_type.empty?
|
47
|
+
raise ArgumentError, "Unable to determine MIME type from response"
|
48
|
+
end
|
49
|
+
|
50
|
+
mime_type
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def windows_platform?
|
56
|
+
RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
57
|
+
end
|
58
|
+
|
59
|
+
def valid_image_file?(file_path, extension)
|
60
|
+
return false unless File.exist?(file_path)
|
14
61
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
62
|
+
# Check magic bytes for common image formats
|
63
|
+
begin
|
64
|
+
header = File.binread(file_path, 10)
|
65
|
+
case extension
|
66
|
+
when '.jpg', '.jpeg'
|
67
|
+
header[0..2].bytes == [0xFF, 0xD8, 0xFF] # JPEG magic bytes
|
68
|
+
when '.png'
|
69
|
+
header[0..7].bytes == [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A] # PNG magic bytes
|
70
|
+
when '.gif'
|
71
|
+
header[0..2] == 'GIF' # GIF magic bytes
|
72
|
+
else
|
73
|
+
false
|
74
|
+
end
|
75
|
+
rescue StandardError
|
76
|
+
false
|
77
|
+
end
|
78
|
+
end
|
20
79
|
end
|
21
80
|
end
|
22
81
|
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
module Axlsx
|
7
|
+
# This module defines some utils related to mime type detection
|
8
|
+
module UriUtils
|
9
|
+
class << self
|
10
|
+
# Performs an HTTP GeT request fetching only headers
|
11
|
+
def fetch_headers(uri, redirect_limit = 5)
|
12
|
+
redirect_count = 0
|
13
|
+
use_get = false
|
14
|
+
|
15
|
+
while redirect_count < redirect_limit
|
16
|
+
case (response = fetch_headers_request(uri, use_get: use_get))
|
17
|
+
when Net::HTTPSuccess
|
18
|
+
return response
|
19
|
+
when Net::HTTPMethodNotAllowed, Net::HTTPNotImplemented
|
20
|
+
fail_response(response) if use_get
|
21
|
+
use_get = true
|
22
|
+
next # Retry current URL with GET
|
23
|
+
when Net::HTTPRedirection
|
24
|
+
uri = follow_redirect(uri, response)
|
25
|
+
redirect_count += 1
|
26
|
+
else
|
27
|
+
fail_response(response)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
raise ArgumentError, "Too many redirects (exceeded #{redirect_limit})"
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def fetch_headers_request(uri, use_get: false)
|
37
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
|
38
|
+
path = build_path(uri)
|
39
|
+
if use_get
|
40
|
+
http.request_get(path) { |response| break(response) } # Exit early with just headers
|
41
|
+
else
|
42
|
+
http.head(path)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def build_path(uri)
|
48
|
+
"#{uri.path.empty? ? '/' : uri.path}#{"?#{uri.query}" if uri.query}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def follow_redirect(original_uri, response)
|
52
|
+
location = response['location']
|
53
|
+
|
54
|
+
if location.nil? || location.empty?
|
55
|
+
raise ArgumentError, "Redirect response missing Location header"
|
56
|
+
end
|
57
|
+
|
58
|
+
if location.start_with?('http://', 'https://')
|
59
|
+
URI.parse(location)
|
60
|
+
else
|
61
|
+
URI.join(original_uri, location)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def fail_response(response)
|
66
|
+
raise ArgumentError, "Failed to fetch resource: #{response.code} #{response.message}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -268,19 +268,19 @@ module Axlsx
|
|
268
268
|
RestrictionValidator.validate :vertical_alignment, VALID_VERTICAL_ALIGNMENT_VALUES, v
|
269
269
|
end
|
270
270
|
|
271
|
-
VALID_CONTENT_TYPE_VALUES = [TABLE_CT, WORKBOOK_CT, APP_CT, RELS_CT, STYLES_CT, XML_CT, WORKSHEET_CT, SHARED_STRINGS_CT, CORE_CT, CHART_CT, JPEG_CT, GIF_CT, PNG_CT, DRAWING_CT, COMMENT_CT, VML_DRAWING_CT, PIVOT_TABLE_CT, PIVOT_TABLE_CACHE_DEFINITION_CT].freeze
|
271
|
+
VALID_CONTENT_TYPE_VALUES = [TABLE_CT, WORKBOOK_CT, APP_CT, RELS_CT, STYLES_CT, THEME_CT, XML_CT, WORKSHEET_CT, SHARED_STRINGS_CT, CORE_CT, CHART_CT, JPEG_CT, GIF_CT, PNG_CT, DRAWING_CT, COMMENT_CT, VML_DRAWING_CT, PIVOT_TABLE_CT, PIVOT_TABLE_CACHE_DEFINITION_CT].freeze
|
272
272
|
|
273
273
|
# Requires that the value is a valid content_type
|
274
|
-
# TABLE_CT, WORKBOOK_CT, APP_CT, RELS_CT, STYLES_CT, XML_CT, WORKSHEET_CT, SHARED_STRINGS_CT, CORE_CT, CHART_CT, DRAWING_CT, COMMENT_CT are allowed
|
274
|
+
# TABLE_CT, WORKBOOK_CT, APP_CT, RELS_CT, STYLES_CT, THEME_CT, XML_CT, WORKSHEET_CT, SHARED_STRINGS_CT, CORE_CT, CHART_CT, DRAWING_CT, COMMENT_CT are allowed
|
275
275
|
# @param [Any] v The value validated
|
276
276
|
def self.validate_content_type(v)
|
277
277
|
RestrictionValidator.validate :content_type, VALID_CONTENT_TYPE_VALUES, v
|
278
278
|
end
|
279
279
|
|
280
|
-
VALID_RELATIONSHIP_TYPE_VALUES = [XML_NS_R, TABLE_R, WORKBOOK_R, WORKSHEET_R, APP_R, RELS_R, CORE_R, STYLES_R, CHART_R, DRAWING_R, IMAGE_R, HYPERLINK_R, SHARED_STRINGS_R, COMMENT_R, VML_DRAWING_R, COMMENT_R_NULL, PIVOT_TABLE_R, PIVOT_TABLE_CACHE_DEFINITION_R].freeze
|
280
|
+
VALID_RELATIONSHIP_TYPE_VALUES = [XML_NS_R, TABLE_R, WORKBOOK_R, WORKSHEET_R, APP_R, RELS_R, CORE_R, STYLES_R, THEME_R, CHART_R, DRAWING_R, IMAGE_R, HYPERLINK_R, SHARED_STRINGS_R, COMMENT_R, VML_DRAWING_R, COMMENT_R_NULL, PIVOT_TABLE_R, PIVOT_TABLE_CACHE_DEFINITION_R].freeze
|
281
281
|
|
282
282
|
# Requires that the value is a valid relationship_type
|
283
|
-
# XML_NS_R, TABLE_R, WORKBOOK_R, WORKSHEET_R, APP_R, RELS_R, CORE_R, STYLES_R, CHART_R, DRAWING_R, IMAGE_R, HYPERLINK_R, SHARED_STRINGS_R are allowed
|
283
|
+
# XML_NS_R, TABLE_R, WORKBOOK_R, WORKSHEET_R, APP_R, RELS_R, CORE_R, STYLES_R, THEME_R, CHART_R, DRAWING_R, IMAGE_R, HYPERLINK_R, SHARED_STRINGS_R are allowed
|
284
284
|
# @param [Any] v The value validated
|
285
285
|
def self.validate_relationship_type(v)
|
286
286
|
RestrictionValidator.validate :relationship_type, VALID_RELATIONSHIP_TYPE_VALUES, v
|
data/lib/axlsx/version.rb
CHANGED
@@ -184,6 +184,12 @@ module Axlsx
|
|
184
184
|
@styles
|
185
185
|
end
|
186
186
|
|
187
|
+
# The theme associated with this workbook
|
188
|
+
# @return [Theme]
|
189
|
+
def theme
|
190
|
+
@theme ||= Theme.new
|
191
|
+
end
|
192
|
+
|
187
193
|
# An array that holds all cells with styles
|
188
194
|
# @return Set
|
189
195
|
def styled_cells
|
@@ -373,6 +379,7 @@ module Axlsx
|
|
373
379
|
r << Relationship.new(pivot_table.cache_definition, PIVOT_TABLE_CACHE_DEFINITION_R, format(PIVOT_TABLE_CACHE_DEFINITION_PN, index + 1))
|
374
380
|
end
|
375
381
|
r << Relationship.new(self, STYLES_R, STYLES_PN)
|
382
|
+
r << Relationship.new(self, THEME_R, THEME_PN)
|
376
383
|
if use_shared_strings
|
377
384
|
r << Relationship.new(self, SHARED_STRINGS_R, SHARED_STRINGS_PN)
|
378
385
|
end
|
@@ -38,7 +38,7 @@ module Axlsx
|
|
38
38
|
# @option [Boolean] show_horizontal_scroll Specifies a boolean value that indicates whether to display the horizontal scroll bar in the user interface.
|
39
39
|
# @option [Boolean] show_vertical_scroll Specifies a boolean value that indicates whether to display the vertical scroll bar.
|
40
40
|
# @option [Boolean] show_sheet_tabs Specifies a boolean value that indicates whether to display the sheet tabs in the user interface.
|
41
|
-
# @option [Integer] tab_ratio Specifies ratio between the workbook tabs bar and the horizontal scroll bar.
|
41
|
+
# @option [Integer] tab_ratio Specifies the ratio between the workbook tabs bar and the horizontal scroll bar (from 0 to 1000, higher values mean more space for tabs). May only be supported on some clients.
|
42
42
|
# @option [Integer] first_sheet Specifies the index to the first sheet in this book view.
|
43
43
|
# @option [Integer] active_tab Specifies an unsignedInt that contains the index to the active sheet in this book view.
|
44
44
|
# @option [Integer] x_window Specifies the X coordinate for the upper left corner of the workbook window. The unit of measurement for this value is twips.
|
data/lib/axlsx.rb
CHANGED
@@ -13,6 +13,7 @@ require 'cgi'
|
|
13
13
|
require 'set'
|
14
14
|
require 'time'
|
15
15
|
require 'uri'
|
16
|
+
require 'net/http'
|
16
17
|
|
17
18
|
require_relative 'axlsx/util/simple_typed_list'
|
18
19
|
require_relative 'axlsx/util/constants'
|
@@ -20,6 +21,7 @@ require_relative 'axlsx/util/validators'
|
|
20
21
|
require_relative 'axlsx/util/accessors'
|
21
22
|
require_relative 'axlsx/util/serialized_attributes'
|
22
23
|
require_relative 'axlsx/util/options_parser'
|
24
|
+
require_relative 'axlsx/util/uri_utils'
|
23
25
|
require_relative 'axlsx/util/mime_type_utils'
|
24
26
|
require_relative 'axlsx/util/buffered_zip_output_stream'
|
25
27
|
require_relative 'axlsx/util/zip_command'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: caxlsx
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Randy Morgan
|
@@ -176,6 +176,7 @@ files:
|
|
176
176
|
- lib/axlsx/stylesheet/table_style.rb
|
177
177
|
- lib/axlsx/stylesheet/table_style_element.rb
|
178
178
|
- lib/axlsx/stylesheet/table_styles.rb
|
179
|
+
- lib/axlsx/stylesheet/theme.rb
|
179
180
|
- lib/axlsx/stylesheet/xf.rb
|
180
181
|
- lib/axlsx/util/accessors.rb
|
181
182
|
- lib/axlsx/util/buffered_zip_output_stream.rb
|
@@ -185,6 +186,7 @@ files:
|
|
185
186
|
- lib/axlsx/util/serialized_attributes.rb
|
186
187
|
- lib/axlsx/util/simple_typed_list.rb
|
187
188
|
- lib/axlsx/util/storage.rb
|
189
|
+
- lib/axlsx/util/uri_utils.rb
|
188
190
|
- lib/axlsx/util/validators.rb
|
189
191
|
- lib/axlsx/util/zip_command.rb
|
190
192
|
- lib/axlsx/version.rb
|