3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

rubyXL で印刷範囲を設定すると死ぬ問題

Posted at

環境

rubyXL 3.4.6

なんでエクセルファイルが死んでしまうん?

印刷範囲は workbook.xml 内の definedNames 要素に、定義名が _xlnm.Print_Area として全シート分定義されます

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<workbook>
  <definedNames>
    <definedName name="_xlnm.Print_Area" localSheetId="0">シート1!$A$1:$I$53</definedName>
    <definedName name="_xlnm.Print_Area" localSheetId="1">シート2!$A$1:$I$53</definedName>
    <definedName name="_xlnm.Print_Area" localSheetId="2">シート3!$A$1:$I$53</definedName>
    <definedName name="_xlnm.Print_Area" localSheetId="3">シート4!$A$1:$I$53</definedName>
    <definedName name="_xlnm.Print_Area" localSheetId="4">シート5!$A$1:$I$53</definedName>
  </definedNames>
</workbook>

また、 app.xmlTitlesOfParts 要素に、 シート名!印刷範囲 の形で記録されます。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Properties>
  <TitlesOfParts>
    <vt:vector baseType="lpstr" size="10">
      <vt:lpstr>シート1</vt:lpstr>
      <vt:lpstr>シート2</vt:lpstr>
      <vt:lpstr>シート3</vt:lpstr>
      <vt:lpstr>シート4</vt:lpstr>
      <vt:lpstr>シート5</vt:lpstr>
      <vt:lpstr>シート1!Print_Area</vt:lpstr>
      <vt:lpstr>シート2!Print_Area</vt:lpstr>
      <vt:lpstr>シート3!Print_Area</vt:lpstr>
      <vt:lpstr>シート4!Print_Area</vt:lpstr>
      <vt:lpstr>シート5!Print_Area</vt:lpstr>
    </vt:vector>
  </TitlesOfParts>
</Properties>

しかし、悲しいことに rubyXL では definedName 要素の name 属性の値がそのまま TitlesOfParts に設定されてしまうのでした。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Properties>
  <TitlesOfParts>
    <vt:vector baseType="lpstr" size="10">
      <vt:lpstr>シート1</vt:lpstr>
      <vt:lpstr>シート2</vt:lpstr>
      <vt:lpstr>シート3</vt:lpstr>
      <vt:lpstr>シート4</vt:lpstr>
      <vt:lpstr>シート5</vt:lpstr>
      <vt:lpstr>_xlnm.Print_Area</vt:lpstr>
      <vt:lpstr>_xlnm.Print_Area</vt:lpstr>
      <vt:lpstr>_xlnm.Print_Area</vt:lpstr>
      <vt:lpstr>_xlnm.Print_Area</vt:lpstr>
      <vt:lpstr>_xlnm.Print_Area</vt:lpstr>
    </vt:vector>
  </TitlesOfParts>
</Properties>

ではどうするか

モンキーパッチ!

require 'rubyXL'
module RubyXL::DocumentPropertiesFilePatch
  XLNM_PATTERN = /^_xlnm\./

  def before_write_xml
    workbook = root.workbook

    self.heading_pairs   = RubyXL::VectorValue.new(:vt_vector => RubyXL::Vector.new(:base_type => 'variant'))
    self.titles_of_parts = RubyXL::VectorValue.new(:vt_vector => RubyXL::Vector.new(:base_type => 'lpstr'))

    worksheets = chartsheets = 0

    workbook.worksheets.each { |sheet|
      add_part_title(sheet.sheet_name)

      case sheet
      when RubyXL::Worksheet  then worksheets += 1
      when RubyXL::Chartsheet then chartsheets += 1
      end
    }
    add_parts_count('Worksheets', worksheets) if worksheets > 0
    add_parts_count('Charts', chartsheets) if chartsheets > 0

    if workbook.defined_names then
      add_parts_count('Named Ranges', workbook.defined_names.size)
      # workbook.defined_names.each { |defined_name| add_part_title(defined_name.name) }

      xlnm_defined_names =  workbook.defined_names.select {|v| v.name =~ XLNM_PATTERN }
      defined_names_without_xlnm = workbook.defined_names - xlnm_defined_names

      if xlnm_defined_names.present? then
        xlnm_defined_names.each do |defined_name|
          sheet_name = /^(.*)!/.match(defined_name.reference)&.values_at(1)&.first
          local_sheet_id = workbook.worksheets.index {|v| v.sheet_name == sheet_name }&.to_i if sheet_name.present?
          if local_sheet_id.present? then
            defined_name.local_sheet_id = local_sheet_id

            name = defined_name.name.gsub(XLNM_PATTERN, "#{sheet_name}!")
            add_part_title(name)
          else
            defined_names_without_xlnm << defined_name
          end
        end
      end

      defined_names_without_xlnm.each { |defined_name| add_part_title(defined_name.name) }
    end

    true
  end
end

RubyXL::DocumentPropertiesFile.prepend RubyXL::DocumentPropertiesFilePatch

おまけ

下記は、実ファイルを気合で書き換えた場合の diff です

@@ -54,6 +54,8 @@ module RubyXL
     end
     private :add_part_title
 
+    XLNM_PATTERN = /^_xlnm\./
+
     def before_write_xml
       workbook = root.workbook
 
@@ -75,7 +77,28 @@ module RubyXL
 
       if workbook.defined_names then
         add_parts_count('Named Ranges', workbook.defined_names.size)
-        workbook.defined_names.each { |defined_name| add_part_title(defined_name.name) }
+        # workbook.defined_names.each { |defined_name| add_part_title(defined_name.name) }
+
+        xlnm_defined_names =  workbook.defined_names.select {|v| v.name =~ XLNM_PATTERN }
+        defined_names_without_xlnm = workbook.defined_names - xlnm_defined_names
+
+        if xlnm_defined_names.present? then
+          xlnm_defined_names.each do |defined_name|
+            sheet_name = /^(.*)!/.match(defined_name.reference)&.values_at(1)&.first
+            local_sheet_id = workbook.worksheets.index {|v| v.sheet_name == sheet_name }&.to_i if sheet_name.present?
+            if local_sheet_id.present? then
+              defined_name.local_sheet_id = local_sheet_id
+
+              name = defined_name.name.gsub(XLNM_PATTERN, "#{sheet_name}!")
+              add_part_title(name)
+            else
+              defined_names_without_xlnm << defined_name
+            end
+          end
+        end
+
+        defined_names_without_xlnm.each { |defined_name| add_part_title(defined_name.name) }
       end
 
       true

ホントはプルリク出したい。

3
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?