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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2962349e5fe1197bffc9c0d28d21be73e83b97f71cf3e8ce86579c07246e5072
4
- data.tar.gz: 67d36e10dfaf54790ffd949e175ba80b24940d331eb9e9b826661158b32c08ee
3
+ metadata.gz: 405eceeb12cbc1b76e2bb0fa1ef24f351f544551268799cffd3f1265fa3bda7f
4
+ data.tar.gz: 13f0be15c97eaf8f21c8e4b4b1c4a2665032f8a8b7fc0ae885a1c1a2fb2cb126
5
5
  SHA512:
6
- metadata.gz: 5f9379732f56d318bb9dba0a0fbf5a9c82e2ec782a97109043eaf6e5272aeb4a2a5eb3b7fa592dbab1d059543ba5dc3dbf3f9fdbc1637bf7c9df42ecacec88ce
7
- data.tar.gz: '080cdb56014357ece810afd62e5e42271f7eb403cdcd62cb63697f894b4814b67b6c2959f11dd911dc98099b5be18e715b0cdc1c27b16cbe95c3b92e6862450f'
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
@@ -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
- # Find relationship target in workbook.xml.rels
165
- rels = parse_xml(@files["xl/_rels/workbook.xml.rels"])
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
- rels = parse_xml(@files["xl/_rels/workbook.xml.rels"])
184
- rel = rels.at_xpath("//xmlns:Relationship[@Id='#{r_id}']")
185
- return unless rel
172
+ path = worksheet_path_for_r_id(r_id)
173
+ return unless path
186
174
 
187
- target = rel["Target"]
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
- target = rel["Target"]
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
- target = ws_drawing_rel["Target"]
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])
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LiquidXlsx
4
- VERSION = "0.2.1"
4
+ VERSION = "0.2.2"
5
5
  end
@@ -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 = package.worksheet_xml(sheet_info[:r_id])
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
- xml = package.worksheet_xml(sheet_info[:r_id])
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)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: liquid_xlsx
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Khlipitkin