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

# 環境
rubyXL 3.4.6 

# なんでエクセルファイルが死んでしまうん？
印刷範囲は `workbook.xml` 内の `definedNames` 要素に、定義名が `_xlnm.Print_Area` として全シート分定義されます

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

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

```xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Properties>
  <TitlesOfParts>
    <vt:vector baseType="lpstr" size="10">
      <vt:lpstr>シート１</vt:lpstr>
      <vt:lpstr>シート２</vt:lpstr>
      <vt:lpstr>シート３</vt:lpstr>
      <vt:lpstr>シート４</vt:lpstr>
      <vt:lpstr>シート５</vt:lpstr>
      <vt:lpstr>シート１!Print_Area</vt:lpstr>
      <vt:lpstr>シート２!Print_Area</vt:lpstr>
      <vt:lpstr>シート３!Print_Area</vt:lpstr>
      <vt:lpstr>シート４!Print_Area</vt:lpstr>
      <vt:lpstr>シート５!Print_Area</vt:lpstr>
    </vt:vector>
  </TitlesOfParts>
</Properties>
```

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

```xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Properties>
  <TitlesOfParts>
    <vt:vector baseType="lpstr" size="10">
      <vt:lpstr>シート１</vt:lpstr>
      <vt:lpstr>シート２</vt:lpstr>
      <vt:lpstr>シート３</vt:lpstr>
      <vt:lpstr>シート４</vt:lpstr>
      <vt:lpstr>シート５</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>

```

# ではどうするか

**モンキーパッチ！**

```ruby
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 です

```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
```
ホントはプルリク出したい。