roo 2.3.0 → 2.10.1
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 +5 -5
- data/.codeclimate.yml +17 -0
- data/.github/issue_template.md +16 -0
- data/.github/pull_request_template.md +14 -0
- data/.github/workflows/pull-request.yml +15 -0
- data/.github/workflows/ruby.yml +34 -0
- data/.gitignore +4 -0
- data/.rubocop.yml +186 -0
- data/CHANGELOG.md +148 -0
- data/Gemfile +4 -4
- data/LICENSE +2 -0
- data/README.md +84 -27
- data/Rakefile +1 -1
- data/lib/roo/base.rb +111 -237
- data/lib/roo/constants.rb +5 -3
- data/lib/roo/csv.rb +106 -85
- data/lib/roo/errors.rb +2 -0
- data/lib/roo/excelx/cell/base.rb +26 -12
- data/lib/roo/excelx/cell/boolean.rb +9 -6
- data/lib/roo/excelx/cell/date.rb +7 -7
- data/lib/roo/excelx/cell/datetime.rb +50 -44
- data/lib/roo/excelx/cell/empty.rb +3 -2
- data/lib/roo/excelx/cell/number.rb +60 -47
- data/lib/roo/excelx/cell/string.rb +3 -3
- data/lib/roo/excelx/cell/time.rb +17 -16
- data/lib/roo/excelx/cell.rb +11 -7
- data/lib/roo/excelx/comments.rb +3 -3
- data/lib/roo/excelx/coordinate.rb +11 -4
- data/lib/roo/excelx/extractor.rb +20 -3
- data/lib/roo/excelx/format.rb +38 -31
- data/lib/roo/excelx/images.rb +26 -0
- data/lib/roo/excelx/relationships.rb +12 -4
- data/lib/roo/excelx/shared.rb +10 -3
- data/lib/roo/excelx/shared_strings.rb +113 -9
- data/lib/roo/excelx/sheet.rb +49 -10
- data/lib/roo/excelx/sheet_doc.rb +101 -48
- data/lib/roo/excelx/styles.rb +4 -4
- data/lib/roo/excelx/workbook.rb +8 -3
- data/lib/roo/excelx.rb +85 -42
- data/lib/roo/formatters/base.rb +15 -0
- data/lib/roo/formatters/csv.rb +84 -0
- data/lib/roo/formatters/matrix.rb +23 -0
- data/lib/roo/formatters/xml.rb +31 -0
- data/lib/roo/formatters/yaml.rb +40 -0
- data/lib/roo/helpers/default_attr_reader.rb +20 -0
- data/lib/roo/helpers/weak_instance_cache.rb +41 -0
- data/lib/roo/open_office.rb +41 -27
- data/lib/roo/spreadsheet.rb +8 -2
- data/lib/roo/tempdir.rb +24 -0
- data/lib/roo/utils.rb +76 -26
- data/lib/roo/version.rb +1 -1
- data/lib/roo.rb +5 -0
- data/roo.gemspec +22 -12
- data/spec/lib/roo/base_spec.rb +65 -3
- data/spec/lib/roo/csv_spec.rb +19 -0
- data/spec/lib/roo/excelx/cell/time_spec.rb +15 -0
- data/spec/lib/roo/excelx/relationships_spec.rb +43 -0
- data/spec/lib/roo/excelx/sheet_doc_spec.rb +11 -0
- data/spec/lib/roo/excelx_spec.rb +237 -5
- data/spec/lib/roo/openoffice_spec.rb +2 -2
- data/spec/lib/roo/spreadsheet_spec.rb +1 -1
- data/spec/lib/roo/strict_spec.rb +43 -0
- data/spec/lib/roo/utils_spec.rb +22 -9
- data/spec/lib/roo/weak_instance_cache_spec.rb +92 -0
- data/spec/lib/roo_spec.rb +0 -0
- data/spec/spec_helper.rb +2 -7
- data/test/excelx/cell/test_attr_reader_default.rb +72 -0
- data/test/excelx/cell/test_base.rb +6 -2
- data/test/excelx/cell/test_boolean.rb +1 -3
- data/test/excelx/cell/test_date.rb +1 -6
- data/test/excelx/cell/test_datetime.rb +7 -10
- data/test/excelx/cell/test_empty.rb +12 -2
- data/test/excelx/cell/test_number.rb +28 -4
- data/test/excelx/cell/test_string.rb +21 -3
- data/test/excelx/cell/test_time.rb +7 -10
- data/test/excelx/test_coordinate.rb +51 -0
- data/test/formatters/test_csv.rb +136 -0
- data/test/formatters/test_matrix.rb +76 -0
- data/test/formatters/test_xml.rb +78 -0
- data/test/formatters/test_yaml.rb +20 -0
- data/test/helpers/test_accessing_files.rb +81 -0
- data/test/helpers/test_comments.rb +43 -0
- data/test/helpers/test_formulas.rb +9 -0
- data/test/helpers/test_labels.rb +103 -0
- data/test/helpers/test_sheets.rb +55 -0
- data/test/helpers/test_styles.rb +62 -0
- data/test/roo/test_base.rb +182 -0
- data/test/roo/test_csv.rb +88 -0
- data/test/roo/test_excelx.rb +360 -0
- data/test/roo/test_libre_office.rb +9 -0
- data/test/roo/test_open_office.rb +289 -0
- data/test/test_helper.rb +129 -14
- data/test/test_roo.rb +60 -1765
- metadata +91 -21
- data/.travis.yml +0 -14
    
        data/lib/roo/base.rb
    CHANGED
    
    | @@ -1,23 +1,39 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
             | 
| 3 | 
            -
            require  | 
| 4 | 
            -
            require  | 
| 5 | 
            -
            require  | 
| 6 | 
            -
            require  | 
| 1 | 
            +
            require "tmpdir"
         | 
| 2 | 
            +
            require "stringio"
         | 
| 3 | 
            +
            require "nokogiri"
         | 
| 4 | 
            +
            require "roo/utils"
         | 
| 5 | 
            +
            require "roo/formatters/base"
         | 
| 6 | 
            +
            require "roo/formatters/csv"
         | 
| 7 | 
            +
            require "roo/formatters/matrix"
         | 
| 8 | 
            +
            require "roo/formatters/xml"
         | 
| 9 | 
            +
            require "roo/formatters/yaml"
         | 
| 7 10 |  | 
| 8 11 | 
             
            # Base class for all other types of spreadsheets
         | 
| 9 12 | 
             
            class Roo::Base
         | 
| 10 13 | 
             
              include Enumerable
         | 
| 14 | 
            +
              include Roo::Formatters::Base
         | 
| 15 | 
            +
              include Roo::Formatters::CSV
         | 
| 16 | 
            +
              include Roo::Formatters::Matrix
         | 
| 17 | 
            +
              include Roo::Formatters::XML
         | 
| 18 | 
            +
              include Roo::Formatters::YAML
         | 
| 11 19 |  | 
| 12 | 
            -
               | 
| 13 | 
            -
               | 
| 14 | 
            -
              MIN_ROW_COL = 0.freeze
         | 
| 20 | 
            +
              MAX_ROW_COL = 999_999
         | 
| 21 | 
            +
              MIN_ROW_COL = 0
         | 
| 15 22 |  | 
| 16 23 | 
             
              attr_reader :headers
         | 
| 17 24 |  | 
| 18 25 | 
             
              # sets the line with attribute names (default: 1)
         | 
| 19 26 | 
             
              attr_accessor :header_line
         | 
| 20 27 |  | 
| 28 | 
            +
              def self.TEMP_PREFIX
         | 
| 29 | 
            +
                warn "[DEPRECATION] please access TEMP_PREFIX via Roo::TEMP_PREFIX"
         | 
| 30 | 
            +
                Roo::TEMP_PREFIX
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
              def self.finalize(object_id)
         | 
| 34 | 
            +
                proc { finalize_tempdirs(object_id) }
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
             | 
| 21 37 | 
             
              def initialize(filename, options = {}, _file_warning = :error, _tmpdir = nil)
         | 
| 22 38 | 
             
                @filename = filename
         | 
| 23 39 | 
             
                @options = options
         | 
| @@ -32,14 +48,17 @@ class Roo::Base | |
| 32 48 | 
             
                @last_column = {}
         | 
| 33 49 |  | 
| 34 50 | 
             
                @header_line = 1
         | 
| 35 | 
            -
              rescue => e # clean up any temp files, but only if an error was raised
         | 
| 36 | 
            -
                close
         | 
| 37 | 
            -
                raise e
         | 
| 38 51 | 
             
              end
         | 
| 39 52 |  | 
| 40 53 | 
             
              def close
         | 
| 41 | 
            -
                 | 
| 42 | 
            -
             | 
| 54 | 
            +
                if self.class.respond_to?(:finalize_tempdirs)
         | 
| 55 | 
            +
                  self.class.finalize_tempdirs(object_id)
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                instance_variables.each do |instance_variable|
         | 
| 59 | 
            +
                  instance_variable_set(instance_variable, nil)
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
             | 
| 43 62 | 
             
                nil
         | 
| 44 63 | 
             
              end
         | 
| 45 64 |  | 
| @@ -48,10 +67,10 @@ class Roo::Base | |
| 48 67 | 
             
              end
         | 
| 49 68 |  | 
| 50 69 | 
             
              # sets the working sheet in the document
         | 
| 51 | 
            -
              # 'sheet' can be a number ( | 
| 70 | 
            +
              # 'sheet' can be a number (0 = first sheet) or the name of a sheet.
         | 
| 52 71 | 
             
              def default_sheet=(sheet)
         | 
| 53 72 | 
             
                validate_sheet!(sheet)
         | 
| 54 | 
            -
                @default_sheet = sheet
         | 
| 73 | 
            +
                @default_sheet = sheet.is_a?(String) ? sheet : sheets[sheet]
         | 
| 55 74 | 
             
                @first_row[sheet] = @last_row[sheet] = @first_column[sheet] = @last_column[sheet] = nil
         | 
| 56 75 | 
             
                @cells_read[sheet] = false
         | 
| 57 76 | 
             
              end
         | 
| @@ -84,7 +103,7 @@ class Roo::Base | |
| 84 103 | 
             
              def collect_last_row_col_for_sheet(sheet)
         | 
| 85 104 | 
             
                first_row = first_column = MAX_ROW_COL
         | 
| 86 105 | 
             
                last_row = last_column = MIN_ROW_COL
         | 
| 87 | 
            -
                @cell[sheet].each_pair do|key, value|
         | 
| 106 | 
            +
                @cell[sheet].each_pair do |key, value|
         | 
| 88 107 | 
             
                  next unless value
         | 
| 89 108 | 
             
                  first_row = [first_row, key.first.to_i].min
         | 
| 90 109 | 
             
                  last_row = [last_row, key.first.to_i].max
         | 
| @@ -94,80 +113,12 @@ class Roo::Base | |
| 94 113 | 
             
                { first_row: first_row, first_column: first_column, last_row: last_row, last_column: last_column }
         | 
| 95 114 | 
             
              end
         | 
| 96 115 |  | 
| 97 | 
            -
              % | 
| 98 | 
            -
                 | 
| 99 | 
            -
             | 
| 100 | 
            -
             | 
| 101 | 
            -
             | 
| 102 | 
            -
                  end                                                                 # end
         | 
| 103 | 
            -
                EOS
         | 
| 104 | 
            -
              end
         | 
| 105 | 
            -
             | 
| 106 | 
            -
              # returns a rectangular area (default: all cells) as yaml-output
         | 
| 107 | 
            -
              # you can add additional attributes with the prefix parameter like:
         | 
| 108 | 
            -
              # oo.to_yaml({"file"=>"flightdata_2007-06-26", "sheet" => "1"})
         | 
| 109 | 
            -
              def to_yaml(prefix = {}, from_row = nil, from_column = nil, to_row = nil, to_column = nil, sheet = default_sheet)
         | 
| 110 | 
            -
                return '' unless first_row # empty result if there is no first_row in a sheet
         | 
| 111 | 
            -
             | 
| 112 | 
            -
                from_row ||= first_row(sheet)
         | 
| 113 | 
            -
                to_row ||= last_row(sheet)
         | 
| 114 | 
            -
                from_column ||= first_column(sheet)
         | 
| 115 | 
            -
                to_column ||= last_column(sheet)
         | 
| 116 | 
            -
             | 
| 117 | 
            -
                result = "--- \n"
         | 
| 118 | 
            -
                from_row.upto(to_row) do |row|
         | 
| 119 | 
            -
                  from_column.upto(to_column) do |col|
         | 
| 120 | 
            -
                    next if empty?(row, col, sheet)
         | 
| 121 | 
            -
             | 
| 122 | 
            -
                    result << "cell_#{row}_#{col}: \n"
         | 
| 123 | 
            -
                    prefix.each do|k, v|
         | 
| 124 | 
            -
                      result << "  #{k}: #{v} \n"
         | 
| 125 | 
            -
                    end
         | 
| 126 | 
            -
                    result << "  row: #{row} \n"
         | 
| 127 | 
            -
                    result << "  col: #{col} \n"
         | 
| 128 | 
            -
                    result << "  celltype: #{celltype(row, col, sheet)} \n"
         | 
| 129 | 
            -
                    value = cell(row, col, sheet)
         | 
| 130 | 
            -
                    if celltype(row, col, sheet) == :time
         | 
| 131 | 
            -
                      value = integer_to_timestring(value)
         | 
| 132 | 
            -
                    end
         | 
| 133 | 
            -
                    result << "  value: #{value} \n"
         | 
| 134 | 
            -
                  end
         | 
| 116 | 
            +
              %i(first_row last_row first_column last_column).each do |key|
         | 
| 117 | 
            +
                ivar = "@#{key}".to_sym
         | 
| 118 | 
            +
                define_method(key) do |sheet = default_sheet|
         | 
| 119 | 
            +
                  read_cells(sheet)
         | 
| 120 | 
            +
                  instance_variable_get(ivar)[sheet] ||= first_last_row_col_for_sheet(sheet)[key]
         | 
| 135 121 | 
             
                end
         | 
| 136 | 
            -
             | 
| 137 | 
            -
                result
         | 
| 138 | 
            -
              end
         | 
| 139 | 
            -
             | 
| 140 | 
            -
              # write the current spreadsheet to stdout or into a file
         | 
| 141 | 
            -
              def to_csv(filename = nil, separator = ',', sheet = default_sheet)
         | 
| 142 | 
            -
                if filename
         | 
| 143 | 
            -
                  File.open(filename, 'w') do |file|
         | 
| 144 | 
            -
                    write_csv_content(file, sheet, separator)
         | 
| 145 | 
            -
                  end
         | 
| 146 | 
            -
                  true
         | 
| 147 | 
            -
                else
         | 
| 148 | 
            -
                  sio = ::StringIO.new
         | 
| 149 | 
            -
                  write_csv_content(sio, sheet, separator)
         | 
| 150 | 
            -
                  sio.rewind
         | 
| 151 | 
            -
                  sio.read
         | 
| 152 | 
            -
                end
         | 
| 153 | 
            -
              end
         | 
| 154 | 
            -
             | 
| 155 | 
            -
              # returns a matrix object from the whole sheet or a rectangular area of a sheet
         | 
| 156 | 
            -
              def to_matrix(from_row = nil, from_column = nil, to_row = nil, to_column = nil, sheet = default_sheet)
         | 
| 157 | 
            -
                require 'matrix'
         | 
| 158 | 
            -
             | 
| 159 | 
            -
                return Matrix.empty unless first_row
         | 
| 160 | 
            -
             | 
| 161 | 
            -
                from_row ||= first_row(sheet)
         | 
| 162 | 
            -
                to_row ||= last_row(sheet)
         | 
| 163 | 
            -
                from_column ||= first_column(sheet)
         | 
| 164 | 
            -
                to_column ||= last_column(sheet)
         | 
| 165 | 
            -
             | 
| 166 | 
            -
                Matrix.rows(from_row.upto(to_row).map do |row|
         | 
| 167 | 
            -
                  from_column.upto(to_column).map do |col|
         | 
| 168 | 
            -
                    cell(row, col, sheet)
         | 
| 169 | 
            -
                  end
         | 
| 170 | 
            -
                end)
         | 
| 171 122 | 
             
              end
         | 
| 172 123 |  | 
| 173 124 | 
             
              def inspect
         | 
| @@ -181,7 +132,7 @@ class Roo::Base | |
| 181 132 | 
             
                options = (args.last.is_a?(Hash) ? args.pop : {})
         | 
| 182 133 |  | 
| 183 134 | 
             
                case args[0]
         | 
| 184 | 
            -
                when  | 
| 135 | 
            +
                when Integer
         | 
| 185 136 | 
             
                  find_by_row(args[0])
         | 
| 186 137 | 
             
                when :all
         | 
| 187 138 | 
             
                  find_by_conditions(options)
         | 
| @@ -223,7 +174,7 @@ class Roo::Base | |
| 223 174 |  | 
| 224 175 | 
             
              def cell_type_by_value(value)
         | 
| 225 176 | 
             
                case value
         | 
| 226 | 
            -
                when  | 
| 177 | 
            +
                when Integer then :float
         | 
| 227 178 | 
             
                when String, Float then :string
         | 
| 228 179 | 
             
                else
         | 
| 229 180 | 
             
                  fail ArgumentError, "Type for #{value} not set"
         | 
| @@ -254,16 +205,16 @@ class Roo::Base | |
| 254 205 | 
             
                    "Number of sheets: #{sheets.size}\n"\
         | 
| 255 206 | 
             
                    "Sheets: #{sheets.join(', ')}\n"
         | 
| 256 207 | 
             
                  n = 1
         | 
| 257 | 
            -
                  sheets.each do|sheet|
         | 
| 208 | 
            +
                  sheets.each do |sheet|
         | 
| 258 209 | 
             
                    self.default_sheet = sheet
         | 
| 259 | 
            -
                    result <<  | 
| 210 | 
            +
                    result << "Sheet " + n.to_s + ":\n"
         | 
| 260 211 | 
             
                    if first_row
         | 
| 261 212 | 
             
                      result << "  First row: #{first_row}\n"
         | 
| 262 213 | 
             
                      result << "  Last row: #{last_row}\n"
         | 
| 263 214 | 
             
                      result << "  First column: #{::Roo::Utils.number_to_letter(first_column)}\n"
         | 
| 264 215 | 
             
                      result << "  Last column: #{::Roo::Utils.number_to_letter(last_column)}"
         | 
| 265 216 | 
             
                    else
         | 
| 266 | 
            -
                      result <<  | 
| 217 | 
            +
                      result << "  - empty -"
         | 
| 267 218 | 
             
                    end
         | 
| 268 219 | 
             
                    result << "\n" if sheet != sheets.last
         | 
| 269 220 | 
             
                    n += 1
         | 
| @@ -272,32 +223,6 @@ class Roo::Base | |
| 272 223 | 
             
                end
         | 
| 273 224 | 
             
              end
         | 
| 274 225 |  | 
| 275 | 
            -
              # returns an XML representation of all sheets of a spreadsheet file
         | 
| 276 | 
            -
              def to_xml
         | 
| 277 | 
            -
                Nokogiri::XML::Builder.new do |xml|
         | 
| 278 | 
            -
                  xml.spreadsheet do
         | 
| 279 | 
            -
                    sheets.each do |sheet|
         | 
| 280 | 
            -
                      self.default_sheet = sheet
         | 
| 281 | 
            -
                      xml.sheet(name: sheet) do |x|
         | 
| 282 | 
            -
                        if first_row && last_row && first_column && last_column
         | 
| 283 | 
            -
                          # sonst gibt es Fehler bei leeren Blaettern
         | 
| 284 | 
            -
                          first_row.upto(last_row) do |row|
         | 
| 285 | 
            -
                            first_column.upto(last_column) do |col|
         | 
| 286 | 
            -
                              next if empty?(row, col)
         | 
| 287 | 
            -
             | 
| 288 | 
            -
                              x.cell(cell(row, col),
         | 
| 289 | 
            -
                                       row: row,
         | 
| 290 | 
            -
                                       column: col,
         | 
| 291 | 
            -
                                       type: celltype(row, col))
         | 
| 292 | 
            -
                            end
         | 
| 293 | 
            -
                          end
         | 
| 294 | 
            -
                        end
         | 
| 295 | 
            -
                      end
         | 
| 296 | 
            -
                    end
         | 
| 297 | 
            -
                  end
         | 
| 298 | 
            -
                end.to_xml
         | 
| 299 | 
            -
              end
         | 
| 300 | 
            -
             | 
| 301 226 | 
             
              # when a method like spreadsheet.a42 is called
         | 
| 302 227 | 
             
              # convert it to a call of spreadsheet.cell('a',42)
         | 
| 303 228 | 
             
              def method_missing(m, *args)
         | 
| @@ -325,6 +250,8 @@ class Roo::Base | |
| 325 250 |  | 
| 326 251 | 
             
              # iterate through all worksheets of a document
         | 
| 327 252 | 
             
              def each_with_pagename
         | 
| 253 | 
            +
                return to_enum(:each_with_pagename) { sheets.size } unless block_given?
         | 
| 254 | 
            +
             | 
| 328 255 | 
             
                sheets.each do |s|
         | 
| 329 256 | 
             
                  yield sheet(s, true)
         | 
| 330 257 | 
             
                end
         | 
| @@ -363,39 +290,42 @@ class Roo::Base | |
| 363 290 | 
             
                  clean_sheet_if_need(options)
         | 
| 364 291 | 
             
                  search_or_set_header(options)
         | 
| 365 292 | 
             
                  headers = @headers ||
         | 
| 366 | 
            -
                             | 
| 367 | 
            -
                              [cell(@header_line, col) | 
| 368 | 
            -
                            end | 
| 293 | 
            +
                            (first_column..last_column).each_with_object({}) do |col, hash|
         | 
| 294 | 
            +
                              hash[cell(@header_line, col)] = col
         | 
| 295 | 
            +
                            end
         | 
| 369 296 |  | 
| 370 297 | 
             
                  @header_line.upto(last_row) do |line|
         | 
| 371 | 
            -
                    yield( | 
| 298 | 
            +
                    yield(headers.each_with_object({}) { |(k, v), hash| hash[k] = cell(line, v) })
         | 
| 372 299 | 
             
                  end
         | 
| 373 300 | 
             
                end
         | 
| 374 301 | 
             
              end
         | 
| 375 302 |  | 
| 376 303 | 
             
              def parse(options = {})
         | 
| 377 | 
            -
                 | 
| 378 | 
            -
             | 
| 379 | 
            -
                  yield(row) if block_given?
         | 
| 380 | 
            -
                  ary << row
         | 
| 304 | 
            +
                results = each(options).map do |row|
         | 
| 305 | 
            +
                  block_given? ? yield(row) : row
         | 
| 381 306 | 
             
                end
         | 
| 382 | 
            -
             | 
| 307 | 
            +
             | 
| 308 | 
            +
                options[:headers] == true ? results : results.drop(1)
         | 
| 383 309 | 
             
              end
         | 
| 384 310 |  | 
| 385 311 | 
             
              def row_with(query, return_headers = false)
         | 
| 386 312 | 
             
                line_no = 0
         | 
| 313 | 
            +
                closest_mismatched_headers = []
         | 
| 387 314 | 
             
                each do |row|
         | 
| 388 315 | 
             
                  line_no += 1
         | 
| 389 316 | 
             
                  headers = query.map { |q| row.grep(q)[0] }.compact
         | 
| 390 | 
            -
             | 
| 391 317 | 
             
                  if headers.length == query.length
         | 
| 392 318 | 
             
                    @header_line = line_no
         | 
| 393 319 | 
             
                    return return_headers ? headers : line_no
         | 
| 394 | 
            -
                   | 
| 395 | 
            -
                     | 
| 320 | 
            +
                  else
         | 
| 321 | 
            +
                    closest_mismatched_headers = headers if headers.length > closest_mismatched_headers.length
         | 
| 322 | 
            +
                    if line_no > 100
         | 
| 323 | 
            +
                      break
         | 
| 324 | 
            +
                    end
         | 
| 396 325 | 
             
                  end
         | 
| 397 326 | 
             
                end
         | 
| 398 | 
            -
                 | 
| 327 | 
            +
                missing_headers = query.select { |q| closest_mismatched_headers.grep(q).empty? }
         | 
| 328 | 
            +
                raise Roo::HeaderRowNotFoundError, missing_headers
         | 
| 399 329 | 
             
              end
         | 
| 400 330 |  | 
| 401 331 | 
             
              protected
         | 
| @@ -408,7 +338,7 @@ class Roo::Base | |
| 408 338 | 
             
                  filename = File.basename(filename, File.extname(filename))
         | 
| 409 339 | 
             
                end
         | 
| 410 340 |  | 
| 411 | 
            -
                if uri?(filename) && (qs_begin = filename.rindex( | 
| 341 | 
            +
                if uri?(filename) && (qs_begin = filename.rindex("?"))
         | 
| 412 342 | 
             
                  filename = filename[0..qs_begin - 1]
         | 
| 413 343 | 
             
                end
         | 
| 414 344 | 
             
                exts = Array(exts)
         | 
| @@ -434,7 +364,7 @@ class Roo::Base | |
| 434 364 | 
             
              # Diese Methode ist eine temp. Loesung, um zu erforschen, ob der
         | 
| 435 365 | 
             
              # Zugriff mit numerischen Keys schneller ist.
         | 
| 436 366 | 
             
              def key_to_num(str)
         | 
| 437 | 
            -
                r, c = str.split( | 
| 367 | 
            +
                r, c = str.split(",")
         | 
| 438 368 | 
             
                [r.to_i, c.to_i]
         | 
| 439 369 | 
             
              end
         | 
| 440 370 |  | 
| @@ -449,10 +379,6 @@ class Roo::Base | |
| 449 379 |  | 
| 450 380 | 
             
              private
         | 
| 451 381 |  | 
| 452 | 
            -
              def track_tmpdir!(tmpdir)
         | 
| 453 | 
            -
                (@tmpdirs ||= []) << tmpdir
         | 
| 454 | 
            -
              end
         | 
| 455 | 
            -
             | 
| 456 382 | 
             
              def clean_sheet_if_need(options)
         | 
| 457 383 | 
             
                return unless options[:clean]
         | 
| 458 384 | 
             
                options.delete(:clean)
         | 
| @@ -500,9 +426,9 @@ class Roo::Base | |
| 500 426 |  | 
| 501 427 | 
             
              def find_by_conditions(options)
         | 
| 502 428 | 
             
                rows = first_row.upto(last_row)
         | 
| 503 | 
            -
                header_for =  | 
| 504 | 
            -
                  [col | 
| 505 | 
            -
                end | 
| 429 | 
            +
                header_for = 1.upto(last_column).each_with_object({}) do |col, hash|
         | 
| 430 | 
            +
                  hash[col] = cell(@header_line, col)
         | 
| 431 | 
            +
                end
         | 
| 506 432 |  | 
| 507 433 | 
             
                # are all conditions met?
         | 
| 508 434 | 
             
                conditions = options[:conditions]
         | 
| @@ -517,9 +443,9 @@ class Roo::Base | |
| 517 443 | 
             
                  rows.map { |i| row(i) }
         | 
| 518 444 | 
             
                else
         | 
| 519 445 | 
             
                  rows.map do |i|
         | 
| 520 | 
            -
                     | 
| 521 | 
            -
                      [header_for.fetch(j) | 
| 522 | 
            -
                    end | 
| 446 | 
            +
                    1.upto(row(i).size).each_with_object({}) do |j, hash|
         | 
| 447 | 
            +
                      hash[header_for.fetch(j)] = cell(i, j)
         | 
| 448 | 
            +
                    end
         | 
| 523 449 | 
             
                  end
         | 
| 524 450 | 
             
                end
         | 
| 525 451 | 
             
              end
         | 
| @@ -535,11 +461,26 @@ class Roo::Base | |
| 535 461 | 
             
                initialize(@filename)
         | 
| 536 462 | 
             
              end
         | 
| 537 463 |  | 
| 464 | 
            +
              def find_basename(filename)
         | 
| 465 | 
            +
                if uri?(filename)
         | 
| 466 | 
            +
                  require "uri"
         | 
| 467 | 
            +
                  uri = URI.parse filename
         | 
| 468 | 
            +
                  File.basename(uri.path)
         | 
| 469 | 
            +
                elsif !is_stream?(filename)
         | 
| 470 | 
            +
                  File.basename(filename)
         | 
| 471 | 
            +
                end
         | 
| 472 | 
            +
              end
         | 
| 473 | 
            +
             | 
| 538 474 | 
             
              def make_tmpdir(prefix = nil, root = nil, &block)
         | 
| 539 | 
            -
                 | 
| 475 | 
            +
                warn "[DEPRECATION] extend Roo::Tempdir and use its .make_tempdir instead"
         | 
| 476 | 
            +
                prefix = "#{Roo::TEMP_PREFIX}#{prefix}"
         | 
| 477 | 
            +
                root ||= ENV["ROO_TMP"]
         | 
| 540 478 |  | 
| 541 | 
            -
                 | 
| 542 | 
            -
                   | 
| 479 | 
            +
                if block_given?
         | 
| 480 | 
            +
                  # folder is deleted at end of block
         | 
| 481 | 
            +
                  ::Dir.mktmpdir(prefix, root, &block)
         | 
| 482 | 
            +
                else
         | 
| 483 | 
            +
                  self.class.make_tempdir(self, prefix, root)
         | 
| 543 484 | 
             
                end
         | 
| 544 485 | 
             
              end
         | 
| 545 486 |  | 
| @@ -552,14 +493,17 @@ class Roo::Base | |
| 552 493 | 
             
              end
         | 
| 553 494 |  | 
| 554 495 | 
             
              def sanitize_value(v)
         | 
| 555 | 
            -
                v.gsub(/[[:cntrl:]]|^[\p{Space}]+|[\p{Space}]+$/,  | 
| 496 | 
            +
                v.gsub(/[[:cntrl:]]|^[\p{Space}]+|[\p{Space}]+$/, "")
         | 
| 556 497 | 
             
              end
         | 
| 557 498 |  | 
| 558 499 | 
             
              def set_headers(hash = {})
         | 
| 559 500 | 
             
                # try to find header row with all values or give an error
         | 
| 560 501 | 
             
                # then create new hash by indexing strings and keeping integers for header array
         | 
| 561 | 
            -
                 | 
| 562 | 
            -
                @headers =  | 
| 502 | 
            +
                header_row = row_with(hash.values, true)
         | 
| 503 | 
            +
                @headers = {}
         | 
| 504 | 
            +
                hash.each_with_index do |(key, _), index|
         | 
| 505 | 
            +
                  @headers[key] = header_index(header_row[index])
         | 
| 506 | 
            +
                end
         | 
| 563 507 | 
             
              end
         | 
| 564 508 |  | 
| 565 509 | 
             
              def header_index(query)
         | 
| @@ -577,7 +521,7 @@ class Roo::Base | |
| 577 521 | 
             
              # converts cell coordinate to numeric values of row,col
         | 
| 578 522 | 
             
              def normalize(row, col)
         | 
| 579 523 | 
             
                if row.is_a?(::String)
         | 
| 580 | 
            -
                  if col.is_a?(:: | 
| 524 | 
            +
                  if col.is_a?(::Integer)
         | 
| 581 525 | 
             
                    # ('A',1):
         | 
| 582 526 | 
             
                    # ('B', 5) -> (5, 2)
         | 
| 583 527 | 
             
                    row, col = col, row
         | 
| @@ -592,17 +536,17 @@ class Roo::Base | |
| 592 536 | 
             
              end
         | 
| 593 537 |  | 
| 594 538 | 
             
              def uri?(filename)
         | 
| 595 | 
            -
                filename.start_with?( | 
| 539 | 
            +
                filename.start_with?("http://", "https://", "ftp://")
         | 
| 596 540 | 
             
              rescue
         | 
| 597 541 | 
             
                false
         | 
| 598 542 | 
             
              end
         | 
| 599 543 |  | 
| 600 544 | 
             
              def download_uri(uri, tmpdir)
         | 
| 601 | 
            -
                require  | 
| 602 | 
            -
                tempfilename = File.join(tmpdir,  | 
| 545 | 
            +
                require "open-uri"
         | 
| 546 | 
            +
                tempfilename = File.join(tmpdir, find_basename(uri))
         | 
| 603 547 | 
             
                begin
         | 
| 604 | 
            -
                  File.open(tempfilename,  | 
| 605 | 
            -
                    open(uri,  | 
| 548 | 
            +
                  File.open(tempfilename, "wb") do |file|
         | 
| 549 | 
            +
                    URI.open(uri, "User-Agent" => "Ruby/#{RUBY_VERSION}") do |net|
         | 
| 606 550 | 
             
                      file.write(net.read)
         | 
| 607 551 | 
             
                    end
         | 
| 608 552 | 
             
                  end
         | 
| @@ -613,15 +557,15 @@ class Roo::Base | |
| 613 557 | 
             
              end
         | 
| 614 558 |  | 
| 615 559 | 
             
              def open_from_stream(stream, tmpdir)
         | 
| 616 | 
            -
                tempfilename = File.join(tmpdir,  | 
| 617 | 
            -
                File.open(tempfilename,  | 
| 560 | 
            +
                tempfilename = File.join(tmpdir, "spreadsheet")
         | 
| 561 | 
            +
                File.open(tempfilename, "wb") do |file|
         | 
| 618 562 | 
             
                  file.write(stream[7..-1])
         | 
| 619 563 | 
             
                end
         | 
| 620 | 
            -
                File.join(tmpdir,  | 
| 564 | 
            +
                File.join(tmpdir, "spreadsheet")
         | 
| 621 565 | 
             
              end
         | 
| 622 566 |  | 
| 623 567 | 
             
              def unzip(filename, tmpdir)
         | 
| 624 | 
            -
                require  | 
| 568 | 
            +
                require "zip/filesystem"
         | 
| 625 569 |  | 
| 626 570 | 
             
                Zip::File.open(filename) do |zip|
         | 
| 627 571 | 
             
                  process_zipfile_packed(zip, tmpdir)
         | 
| @@ -633,8 +577,8 @@ class Roo::Base | |
| 633 577 | 
             
                case sheet
         | 
| 634 578 | 
             
                when nil
         | 
| 635 579 | 
             
                  fail ArgumentError, "Error: sheet 'nil' not valid"
         | 
| 636 | 
            -
                when  | 
| 637 | 
            -
                  sheets.fetch(sheet | 
| 580 | 
            +
                when Integer
         | 
| 581 | 
            +
                  sheets.fetch(sheet) do
         | 
| 638 582 | 
             
                    fail RangeError, "sheet index #{sheet} not found"
         | 
| 639 583 | 
             
                  end
         | 
| 640 584 | 
             
                when String
         | 
| @@ -646,90 +590,20 @@ class Roo::Base | |
| 646 590 | 
             
                end
         | 
| 647 591 | 
             
              end
         | 
| 648 592 |  | 
| 649 | 
            -
              def process_zipfile_packed(zip, tmpdir, path =  | 
| 593 | 
            +
              def process_zipfile_packed(zip, tmpdir, path = "")
         | 
| 650 594 | 
             
                if zip.file.file? path
         | 
| 651 595 | 
             
                  # extract and return filename
         | 
| 652 | 
            -
                  File.open(File.join(tmpdir, path),  | 
| 596 | 
            +
                  File.open(File.join(tmpdir, path), "wb") do |file|
         | 
| 653 597 | 
             
                    file.write(zip.read(path))
         | 
| 654 598 | 
             
                  end
         | 
| 655 599 | 
             
                  File.join(tmpdir, path)
         | 
| 656 600 | 
             
                else
         | 
| 657 601 | 
             
                  ret = nil
         | 
| 658 | 
            -
                  path +=  | 
| 602 | 
            +
                  path += "/" unless path.empty?
         | 
| 659 603 | 
             
                  zip.dir.foreach(path) do |filename|
         | 
| 660 604 | 
             
                    ret = process_zipfile_packed(zip, tmpdir, path + filename)
         | 
| 661 605 | 
             
                  end
         | 
| 662 606 | 
             
                  ret
         | 
| 663 607 | 
             
                end
         | 
| 664 608 | 
             
              end
         | 
| 665 | 
            -
             | 
| 666 | 
            -
              # Write all cells to the csv file. File can be a filename or nil. If the this
         | 
| 667 | 
            -
              # parameter is nil the output goes to STDOUT
         | 
| 668 | 
            -
              def write_csv_content(file = nil, sheet = nil, separator = ',')
         | 
| 669 | 
            -
                file ||= STDOUT
         | 
| 670 | 
            -
                return unless first_row(sheet) # The sheet is empty
         | 
| 671 | 
            -
             | 
| 672 | 
            -
                1.upto(last_row(sheet)) do |row|
         | 
| 673 | 
            -
                  1.upto(last_column(sheet)) do |col|
         | 
| 674 | 
            -
                    file.print(separator) if col > 1
         | 
| 675 | 
            -
                    file.print cell_to_csv(row, col, sheet)
         | 
| 676 | 
            -
                  end
         | 
| 677 | 
            -
                  file.print("\n")
         | 
| 678 | 
            -
                end
         | 
| 679 | 
            -
              end
         | 
| 680 | 
            -
             | 
| 681 | 
            -
              # The content of a cell in the csv output
         | 
| 682 | 
            -
              def cell_to_csv(row, col, sheet)
         | 
| 683 | 
            -
                return '' if empty?(row, col, sheet)
         | 
| 684 | 
            -
             | 
| 685 | 
            -
                onecell = cell(row, col, sheet)
         | 
| 686 | 
            -
             | 
| 687 | 
            -
                case celltype(row, col, sheet)
         | 
| 688 | 
            -
                when :string
         | 
| 689 | 
            -
                  %("#{onecell.gsub('"', '""')}") unless onecell.empty?
         | 
| 690 | 
            -
                when :boolean
         | 
| 691 | 
            -
                  # TODO: this only works for excelx
         | 
| 692 | 
            -
                  onecell = self.sheet_for(sheet).cells[[row, col]].formatted_value
         | 
| 693 | 
            -
                  %("#{onecell.gsub('"', '""').downcase}")
         | 
| 694 | 
            -
                when :float, :percentage
         | 
| 695 | 
            -
                  if onecell == onecell.to_i
         | 
| 696 | 
            -
                    onecell.to_i.to_s
         | 
| 697 | 
            -
                  else
         | 
| 698 | 
            -
                    onecell.to_s
         | 
| 699 | 
            -
                  end
         | 
| 700 | 
            -
                when :formula
         | 
| 701 | 
            -
                  case onecell
         | 
| 702 | 
            -
                  when String
         | 
| 703 | 
            -
                    %("#{onecell.gsub('"', '""')}") unless onecell.empty?
         | 
| 704 | 
            -
                  when Float
         | 
| 705 | 
            -
                    if onecell == onecell.to_i
         | 
| 706 | 
            -
                      onecell.to_i.to_s
         | 
| 707 | 
            -
                    else
         | 
| 708 | 
            -
                      onecell.to_s
         | 
| 709 | 
            -
                    end
         | 
| 710 | 
            -
                  when DateTime
         | 
| 711 | 
            -
                    onecell.to_s
         | 
| 712 | 
            -
                  else
         | 
| 713 | 
            -
                    fail "unhandled onecell-class #{onecell.class}"
         | 
| 714 | 
            -
                  end
         | 
| 715 | 
            -
                when :date, :datetime
         | 
| 716 | 
            -
                  onecell.to_s
         | 
| 717 | 
            -
                when :time
         | 
| 718 | 
            -
                  integer_to_timestring(onecell)
         | 
| 719 | 
            -
                when :link
         | 
| 720 | 
            -
                  %("#{onecell.url.gsub('"', '""')}")
         | 
| 721 | 
            -
                else
         | 
| 722 | 
            -
                  fail "unhandled celltype #{celltype(row, col, sheet)}"
         | 
| 723 | 
            -
                end || ''
         | 
| 724 | 
            -
              end
         | 
| 725 | 
            -
             | 
| 726 | 
            -
              # converts an integer value to a time string like '02:05:06'
         | 
| 727 | 
            -
              def integer_to_timestring(content)
         | 
| 728 | 
            -
                h = (content / 3600.0).floor
         | 
| 729 | 
            -
                content -= h * 3600
         | 
| 730 | 
            -
                m = (content / 60.0).floor
         | 
| 731 | 
            -
                content -= m * 60
         | 
| 732 | 
            -
                s = content
         | 
| 733 | 
            -
                sprintf('%02d:%02d:%02d', h, m, s)
         | 
| 734 | 
            -
              end
         | 
| 735 609 | 
             
            end
         | 
    
        data/lib/roo/constants.rb
    CHANGED
    
    | @@ -1,5 +1,7 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module Roo
         | 
| 2 | 
            -
              ROO_EXCEL_NOTICE   = "Excel support has been extracted to roo-xls due to its dependency on the GPL'd spreadsheet gem. Install roo-xls to use Roo::Excel." | 
| 3 | 
            -
              ROO_EXCELML_NOTICE = "Excel SpreadsheetML support has been extracted to roo-xls. Install roo-xls to use Roo::Excel2003XML." | 
| 4 | 
            -
              ROO_GOOGLE_NOTICE = "Google support has been extracted to roo-google. Install roo-google to use Roo::Google." | 
| 4 | 
            +
              ROO_EXCEL_NOTICE   = "Excel support has been extracted to roo-xls due to its dependency on the GPL'd spreadsheet gem. Install roo-xls to use Roo::Excel."
         | 
| 5 | 
            +
              ROO_EXCELML_NOTICE = "Excel SpreadsheetML support has been extracted to roo-xls. Install roo-xls to use Roo::Excel2003XML."
         | 
| 6 | 
            +
              ROO_GOOGLE_NOTICE = "Google support has been extracted to roo-google. Install roo-google to use Roo::Google."
         | 
| 5 7 | 
             
            end
         |