Class: SwarmSDK::Tools::DocumentConverters::XlsxConverter

Inherits:
BaseConverter
  • Object
show all
Defined in:
lib/swarm_sdk/tools/document_converters/xlsx_converter.rb

Overview

Converts XLSX/XLS spreadsheets to text with image extraction

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from BaseConverter

available?, gem_available?

Class Method Details

.extensionsObject



17
18
19
# File 'lib/swarm_sdk/tools/document_converters/xlsx_converter.rb', line 17

def extensions
  [".xlsx", ".xls"]
end

.format_nameObject



13
14
15
# File 'lib/swarm_sdk/tools/document_converters/xlsx_converter.rb', line 13

def format_name
  "XLSX/XLS"
end

.gem_nameObject



9
10
11
# File 'lib/swarm_sdk/tools/document_converters/xlsx_converter.rb', line 9

def gem_name
  "roo"
end

.xls_gem_available?Boolean

XLS files require an additional gem

Returns:

  • (Boolean)


22
23
24
# File 'lib/swarm_sdk/tools/document_converters/xlsx_converter.rb', line 22

def xls_gem_available?
  gem_available?("roo-xls")
end

Instance Method Details

#convert(file_path) ⇒ String, RubyLLM::Content

Convert a spreadsheet to text/content

Parameters:

  • file_path (String)

    Path to the spreadsheet file

Returns:

  • (String, RubyLLM::Content)

    Converted content or error message



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/swarm_sdk/tools/document_converters/xlsx_converter.rb', line 30

def convert(file_path)
  unless self.class.available?
    return unsupported_format_reminder(self.class.format_name, self.class.gem_name)
  end

  # Check for legacy XLS files
  extension = File.extname(file_path).downcase
  if extension == ".xls" && !self.class.xls_gem_available?
    return error("Legacy .xls files require the 'roo-xls' gem. Install with: gem install roo-xls")
  end

  spreadsheet = nil

  begin
    require "roo"
    require "csv"
    require "tmpdir"

    spreadsheet = Roo::Spreadsheet.open(file_path)

    # Extract images from all sheets
    image_paths = extract_images(spreadsheet, file_path)

    output = []
    output << "Spreadsheet: #{File.basename(file_path)}"
    output << "Sheets: #{spreadsheet.sheets.size}"
    output << "=" * 60
    output << ""

    spreadsheet.sheets.each_with_index do |sheet_name, sheet_idx|
      sheet = spreadsheet.sheet(sheet_name)

      output << "Sheet #{sheet_idx + 1}: #{sheet_name}"
      output << "-" * 60

      # Add sheet dimensions
      first_row = sheet.first_row
      last_row = sheet.last_row
      first_col = sheet.first_column
      last_col = sheet.last_column

      if first_row && last_row && first_col && last_col
        row_count = last_row - first_row + 1
        col_count = last_col - first_col + 1
        output << "Dimensions: #{row_count} rows × #{col_count} columns"
        output << ""
      else
        output << "(Empty sheet)"
        output << ""
        next
      end

      # Extract data rows
      sheet.each_row_streaming(pad_cells: true) do |row|
        row_values = row.map do |cell|
          format_cell_value(cell)
        end

        # Skip completely empty rows
        next if row_values.all? { |v| v.nil? || v.empty? }

        # Format as CSV with proper escaping
        output << CSV.generate_line(row_values).chomp
      end

      output << ""
    end

    text_content = output.join("\n")

    # If there are images, return Content with attachments
    if image_paths.any?
      content = RubyLLM::Content.new(text_content)
      image_paths.each do |image_path|
        content.add_attachment(image_path)
      end
      content
    else
      # No images, return just text
      text_content
    end
  rescue ArgumentError => e
    error("Failed to open spreadsheet: #{e.message}")
  rescue RangeError => e
    error("Sheet access error: #{e.message}")
  rescue Zip::Error => e
    error("Corrupted or invalid XLSX file: #{e.message}")
  rescue IOError => e
    error("File reading error: #{e.message}")
  rescue StandardError => e
    error("Failed to parse spreadsheet: #{e.message}")
  ensure
    # Always clean up resources
    spreadsheet.close if spreadsheet&.respond_to?(:close)
  end
end