liquid_xlsx 0.2.1 → 0.2.2
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 +19 -0
- data/lib/liquid_xlsx/package.rb +27 -24
- data/lib/liquid_xlsx/version.rb +1 -1
- data/lib/liquid_xlsx/workbook.rb +18 -5
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 405eceeb12cbc1b76e2bb0fa1ef24f351f544551268799cffd3f1265fa3bda7f
|
|
4
|
+
data.tar.gz: 13f0be15c97eaf8f21c8e4b4b1c4a2665032f8a8b7fc0ae885a1c1a2fb2cb126
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1677f21ae1c65f95cf62fe1728cdeadef70e96a051ed33a1ed7acf57eb2b9067e00f3f4d1901caf20d9d23f37edbec601cef7115704404e75eaaca160d1d6405
|
|
7
|
+
data.tar.gz: 9eda27bf7211ffc329af17e9ddf9992dcda7e551ff9526d62afb8df870e56d88381cd369acf120704ede782e10ded1650c410e97d71ebbf5159bff46b311e630
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.2 (2026-08-14)
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Templates whose relationship targets are absolute are rendered again. OPC
|
|
8
|
+
allows a `Target` to be relative to the declaring part
|
|
9
|
+
(`worksheets/sheet1.xml`) or absolute from the package root
|
|
10
|
+
(`/xl/worksheets/sheet1.xml`); Excel writes the first form, 1C exports and
|
|
11
|
+
some other generators write the second. The package layer joined every target
|
|
12
|
+
to its base folder, so an absolute one resolved to
|
|
13
|
+
`xl/xl/worksheets/sheet1.xml`, the lookup found nothing, and the sheet was
|
|
14
|
+
skipped — the caller received a copy of the template with `{{ tags }}` still
|
|
15
|
+
in it and no error anywhere. Targets are now resolved with a single helper
|
|
16
|
+
that honours both forms.
|
|
17
|
+
- A sheet listed in `workbook.xml` whose worksheet part cannot be found now
|
|
18
|
+
raises `InvalidXlsxError` instead of being skipped silently. Skipping turned
|
|
19
|
+
a broken package into an unrendered output file, which is far harder to
|
|
20
|
+
diagnose than a failed render.
|
|
21
|
+
|
|
3
22
|
## 0.2.1 (2026-08-09)
|
|
4
23
|
|
|
5
24
|
### Fixed
|
data/lib/liquid_xlsx/package.rb
CHANGED
|
@@ -161,33 +161,18 @@ module LiquidXlsx
|
|
|
161
161
|
# @param r_id [String]
|
|
162
162
|
# @return [String, nil]
|
|
163
163
|
def worksheet_xml(r_id)
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
rel = rels.at_xpath("//xmlns:Relationship[@Id='#{r_id}']")
|
|
167
|
-
return nil unless rel
|
|
168
|
-
|
|
169
|
-
target = rel["Target"]
|
|
170
|
-
path = "xl/#{target}"
|
|
171
|
-
|
|
172
|
-
# Handle paths like xl/worksheets/sheet1.xml vs xl/../xl/worksheets/sheet1.xml
|
|
173
|
-
# Normalize the path
|
|
174
|
-
normalized = Pathname.new(path).cleanpath.to_s
|
|
175
|
-
normalized = normalized.sub(%r{\A/?}, "")
|
|
176
|
-
@files[normalized]
|
|
164
|
+
path = worksheet_path_for_r_id(r_id)
|
|
165
|
+
path && @files[path]
|
|
177
166
|
end
|
|
178
167
|
|
|
179
168
|
# Save worksheet XML back.
|
|
180
169
|
# @param r_id [String]
|
|
181
170
|
# @param xml [String]
|
|
182
171
|
def save_worksheet_xml(r_id, xml)
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
return unless rel
|
|
172
|
+
path = worksheet_path_for_r_id(r_id)
|
|
173
|
+
return unless path
|
|
186
174
|
|
|
187
|
-
|
|
188
|
-
path = "xl/#{target}"
|
|
189
|
-
normalized = Pathname.new(path).cleanpath.to_s.sub(%r{\A/?}, "")
|
|
190
|
-
@files[normalized] = xml
|
|
175
|
+
@files[path] = xml
|
|
191
176
|
end
|
|
192
177
|
|
|
193
178
|
# Get calc chain XML.
|
|
@@ -442,8 +427,7 @@ module LiquidXlsx
|
|
|
442
427
|
rel = rels.at_xpath("//xmlns:Relationship[@Id='#{r_id}']")
|
|
443
428
|
return nil unless rel
|
|
444
429
|
|
|
445
|
-
|
|
446
|
-
Pathname.new("xl/#{target}").cleanpath.to_s.sub(%r{\A/?}, "")
|
|
430
|
+
resolve_target("xl", rel["Target"])
|
|
447
431
|
end
|
|
448
432
|
|
|
449
433
|
# Get all sheet names from workbook.xml.
|
|
@@ -454,6 +438,26 @@ module LiquidXlsx
|
|
|
454
438
|
|
|
455
439
|
private
|
|
456
440
|
|
|
441
|
+
# Turn a relationship Target into a package path.
|
|
442
|
+
#
|
|
443
|
+
# OPC allows a Target to be either relative to the folder of the part that
|
|
444
|
+
# declares it ("worksheets/sheet1.xml", "../drawings/drawing1.xml") or
|
|
445
|
+
# absolute from the package root ("/xl/worksheets/sheet1.xml"). Excel writes
|
|
446
|
+
# the relative form, but 1C and some other generators write the absolute one.
|
|
447
|
+
# Joining an absolute target to the base gave "xl/xl/worksheets/sheet1.xml":
|
|
448
|
+
# the lookup returned nil, `Workbook#render_existing_sheets` skipped the
|
|
449
|
+
# sheet, and the template came back copied instead of rendered.
|
|
450
|
+
#
|
|
451
|
+
# @param base_dir [String] folder of the declaring part, e.g. "xl"
|
|
452
|
+
# @param target [String, nil] the Target attribute value
|
|
453
|
+
# @return [String, nil] normalized package path
|
|
454
|
+
def resolve_target(base_dir, target)
|
|
455
|
+
return nil if target.nil? || target.empty?
|
|
456
|
+
|
|
457
|
+
path = target.start_with?("/") ? target : "#{base_dir}/#{target}"
|
|
458
|
+
Pathname.new(path).cleanpath.to_s.sub(%r{\A/+}, "")
|
|
459
|
+
end
|
|
460
|
+
|
|
457
461
|
# The size is declared in bytes: String#size counts characters, so on UTF-8
|
|
458
462
|
# content it would disagree with what actually gets written.
|
|
459
463
|
# See ZIP_SIZE_KEYWORD — rubyzip 2.x has no such keyword and needs none.
|
|
@@ -534,8 +538,7 @@ module LiquidXlsx
|
|
|
534
538
|
# Merge freshly built drawing anchors and their relationships into the
|
|
535
539
|
# drawing part already attached to the worksheet, remapping rIds.
|
|
536
540
|
def merge_into_existing_drawing(ws_drawing_rel, new_drawing_xml, new_rels_xml)
|
|
537
|
-
|
|
538
|
-
drawing_path = Pathname.new("xl/worksheets/#{target}").cleanpath.to_s.sub(%r{\A/?}, "")
|
|
541
|
+
drawing_path = resolve_target("xl/worksheets", ws_drawing_rel["Target"])
|
|
539
542
|
drawing_rels_path = "#{drawing_path.sub('xl/drawings/', 'xl/drawings/_rels/')}.rels"
|
|
540
543
|
|
|
541
544
|
existing_doc = parse_xml(@files[drawing_path])
|
data/lib/liquid_xlsx/version.rb
CHANGED
data/lib/liquid_xlsx/workbook.rb
CHANGED
|
@@ -47,10 +47,25 @@ module LiquidXlsx
|
|
|
47
47
|
|
|
48
48
|
private
|
|
49
49
|
|
|
50
|
+
# Worksheet part behind a <sheet> entry of workbook.xml.
|
|
51
|
+
#
|
|
52
|
+
# A workbook that lists a sheet whose part cannot be resolved is broken, and
|
|
53
|
+
# skipping it silently used to hand the caller an untouched copy of the
|
|
54
|
+
# template: no error anywhere, just `{{ tags }}` left in the "rendered"
|
|
55
|
+
# file. Fail loudly instead — the usual cause is a rels Target the package
|
|
56
|
+
# layer could not resolve.
|
|
57
|
+
def worksheet_xml!(sheet_info)
|
|
58
|
+
xml = package.worksheet_xml(sheet_info[:r_id])
|
|
59
|
+
return xml if xml
|
|
60
|
+
|
|
61
|
+
raise InvalidXlsxError,
|
|
62
|
+
"Worksheet part for sheet '#{sheet_info[:name]}' (#{sheet_info[:r_id]}) " \
|
|
63
|
+
"is missing from the package"
|
|
64
|
+
end
|
|
65
|
+
|
|
50
66
|
def render_existing_sheets(data, all_image_ops = [])
|
|
51
67
|
package.sheets.each do |sheet_info|
|
|
52
|
-
xml =
|
|
53
|
-
next unless xml
|
|
68
|
+
xml = worksheet_xml!(sheet_info)
|
|
54
69
|
|
|
55
70
|
image_ops = []
|
|
56
71
|
|
|
@@ -68,8 +83,7 @@ module LiquidXlsx
|
|
|
68
83
|
# Phase 0: Save original sheet XMLs before rendering mutates them
|
|
69
84
|
original_xmls = {}
|
|
70
85
|
package.sheets.each do |sheet_info|
|
|
71
|
-
|
|
72
|
-
original_xmls[sheet_info[:name]] = xml if xml
|
|
86
|
+
original_xmls[sheet_info[:name]] = worksheet_xml!(sheet_info)
|
|
73
87
|
end
|
|
74
88
|
|
|
75
89
|
# Phase 0.5: Identify control and template sheets
|
|
@@ -86,7 +100,6 @@ module LiquidXlsx
|
|
|
86
100
|
next if template_names_from_tags.include?(sheet_info[:name])
|
|
87
101
|
|
|
88
102
|
xml = original_xmls[sheet_info[:name]]
|
|
89
|
-
next unless xml
|
|
90
103
|
|
|
91
104
|
image_ops = []
|
|
92
105
|
worksheet = Worksheet.new(xml, sheet_info[:name], @shared_strings)
|