tabledata 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0542e111de916c971f41cdec6401c4a176225544
4
+ data.tar.gz: 7ead0e2dfc2147a68588285c2bb84d097bb91b32
5
+ SHA512:
6
+ metadata.gz: a08b73d455e4f262dbf8de1641eee2a8187b7471a07aa9216e981b7529a42cb52aab711fe861c599e99ab12762a793f2236f808f5076608fb91db353867272ff
7
+ data.tar.gz: 24c0ebf7907290a4ea8267d80f6473554b72407b2be7a21930881d2db5b811eb8d973eb7256fcb670cc4d9227be6f10e360f548a6407f25c0c728196c2fbfb31
@@ -0,0 +1,8 @@
1
+ Copyright (c) 2013, Stefan Rusterholz <stefan.rusterholz@gmail.com>
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5
+
6
+ Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7
+ Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,55 @@
1
+ README
2
+ ======
3
+
4
+
5
+
6
+ Summary
7
+ -------
8
+
9
+ Read tabular data from various formats, like Excel .xls, Excel .xlsx, CSV.
10
+
11
+
12
+
13
+ Installation
14
+ ------------
15
+
16
+ `gem install tables`
17
+
18
+
19
+
20
+ Usage
21
+ -----
22
+
23
+ table = TableData.table_from_file(path)
24
+
25
+
26
+
27
+ Description
28
+ -----------
29
+
30
+ Read tabular data from various formats.
31
+
32
+
33
+
34
+ Known Issues
35
+ ------------
36
+
37
+ * The 'spreadsheet' gem on which this gem depends does not yet correctly work with ruby 2.0.
38
+
39
+
40
+
41
+ Links
42
+ -----
43
+
44
+ * [Online API Documentation](http://rdoc.info/github/apeiros/tabledata/)
45
+ * [Public Repository](https://github.com/apeiros/tabledata)
46
+ * [Bug Reporting](https://github.com/apeiros/tabledata/issues)
47
+ * [RubyGems Site](https://rubygems.org/gems/tabledata)
48
+
49
+
50
+
51
+ License
52
+ -------
53
+
54
+ You can use this code under the {file:LICENSE.txt BSD-2-Clause License}, free of charge.
55
+ If you need a different license, please ask the author.
@@ -0,0 +1,10 @@
1
+ $LOAD_PATH.unshift(File.expand_path('../rake/lib', __FILE__))
2
+ Dir.glob(File.expand_path('../rake/tasks/**/*.{rake,task,rb}', __FILE__)) do |task_file|
3
+ begin
4
+ import task_file
5
+ rescue LoadError => e
6
+ warn "Failed to load task file #{task_file}"
7
+ warn " #{e.class} #{e.message}"
8
+ warn " #{e.backtrace.first}"
9
+ end
10
+ end
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+
3
+ require 'tabledata/version'
4
+ require 'tabledata/table'
5
+
6
+ # Tables
7
+ # Read tabular data from various formats.
8
+ module TableData
9
+ module_function
10
+
11
+ # @see TableData::Table::from_file Full documentation
12
+ def table_from_file(path, options=nil)
13
+ Table.from_file(path, options)
14
+ end
15
+
16
+ # NOT IMPLEMENTED!
17
+ #
18
+ # @return [TableData::Tables]
19
+ def tables_from_file(path)
20
+ raise "Unimplemented"
21
+ end
22
+
23
+ def require_library(name, message)
24
+ $stdout, oldstdout = StringIO.new, $stdout
25
+ require name
26
+ rescue LoadError => error
27
+ if error.message =~ /cannot load such file -- #{Regexp.escape(name)}/ then
28
+ raise LibraryMissingError.new(name, message, error)
29
+ else
30
+ raise
31
+ end
32
+ ensure
33
+ $stdout = oldstdout
34
+ end
35
+ end
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+
3
+ module TableData
4
+ class CoercingRow < Row
5
+ def initialize(table, index, data, coercions)
6
+ @coercions = coercions
7
+ super(table, index, data.map.with_index { |value, col| coerce(col, value) })
8
+ end
9
+
10
+ def coerce(column, value)
11
+ coercer = @coercions[column]
12
+ coercer ? coercer.call(value) : value
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+
3
+ module TableData
4
+ module Coercion
5
+ @coerce = {}
6
+
7
+ def self.[](key)
8
+ @coerce[key]
9
+ end
10
+
11
+ def self.coercing(key, &block)
12
+ @coerce[key] = block
13
+ end
14
+
15
+ coercing String do |value, format|
16
+ value.to_s
17
+ end
18
+
19
+ coercing Integer do |value, format|
20
+ Integer(value, format.fetch(:base, 10))
21
+ end
22
+
23
+ coercing Date do |value, format|
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,47 @@
1
+ # encoding: utf-8
2
+
3
+ module TableData
4
+ class Column
5
+ attr_reader :table
6
+ attr_reader :index
7
+
8
+ include Enumerable
9
+
10
+ def initialize(table, index)
11
+ @table = table
12
+ @index = index
13
+ end
14
+
15
+ def header
16
+ @table.column_header(@index)
17
+ end
18
+
19
+ def accessor
20
+ @table.column_accessor(@index)
21
+ end
22
+
23
+ def [](*args)
24
+ rows = @table.rows[*args]
25
+
26
+ if rows.is_a?(Array) # slice
27
+ rows.map { |row| row.at(@index) }
28
+ else # single row
29
+ rows.at(@index)
30
+ end
31
+ end
32
+
33
+ def each
34
+ return enum_for(__method__) unless block_given?
35
+
36
+ @table.each do |row|
37
+ yield row.at(@index)
38
+ end
39
+ end
40
+
41
+ def to_a(include_header=true)
42
+ data = @table.data.transpose[@index]
43
+
44
+ include_header || !@table.headers? ? data : data[1..-1]
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,76 @@
1
+ # encoding: utf-8
2
+
3
+ # Encoding::Windows_1252
4
+ # Encoding::MacRoman
5
+ # Encoding::UTF_8
6
+ # Encoding::ISO8859_15
7
+
8
+ require 'tabledata/exceptions'
9
+
10
+ module TableData
11
+ module Detection
12
+ UnlikelyCharsWin1252 = "\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xAB\xAC\xAD" \
13
+ "\xAE\xAF\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xBB" \
14
+ "\xBC\xBD\xBE\xBF\xD7\xF7"
15
+ UnlikelyCharsIso8859_1 = ""
16
+ UnlikelyCharsMacRoman = ""
17
+
18
+ UmlautsMac = "äöü".encode(Encoding::MacRoman).force_encoding(Encoding::BINARY)
19
+ UmlautsWin = "äöü".encode(Encoding::Windows_1252).force_encoding(Encoding::BINARY)
20
+
21
+ DiacritsMac = "âàéèô".encode(Encoding::MacRoman).force_encoding(Encoding::BINARY)
22
+ DiacritsWin = "âàéèô".encode(Encoding::Windows_1252).force_encoding(Encoding::BINARY)
23
+
24
+ module_function
25
+ def force_guessed_encoding!(string)
26
+ return string if string.force_encoding(Encoding::UTF_8).valid_encoding?
27
+ string.force_encoding(Encoding::BINARY)
28
+
29
+ # check for non-mapped codepoints
30
+ possible_encodings = [Encoding::Windows_1252, Encoding::ISO8859_15, Encoding::MacRoman]
31
+ possible_encodings.delete(Encoding::ISO8859_15) if string =~ /[\x80-\x9f]/n
32
+ possible_encodings.delete(Encoding::Windows_1252) if string =~ /[\x81\x8D\x8F\x90\x9D]/n
33
+ return string.force_encoding(possible_encodings.first) if possible_encodings.size == 1
34
+
35
+ # # check for occurrences of characters with weighted expectancy
36
+ # # e.g. a "§" is quite unlikely
37
+ # win = string[0,10_000].count(UnlikelyCharsWin1252)
38
+ # iso = string[0,10_000].count(UnlikelyCharsIso8859_1)
39
+ # mac = string[0,10_000].count(UnlikelyCharsMacRoman)
40
+
41
+ # Check occurrences of äöü
42
+ case string[0,10_000].count(UmlautsMac) <=> string[0,10_000].count(UmlautsWin)
43
+ when -1 then return string.force_encoding(Encoding::Windows_1252)
44
+ when 1 then return string.force_encoding(Encoding::MacRoman)
45
+ end
46
+
47
+ # Check occurrences of âàéèô
48
+ case string[0,10_000].count(DiacritsMac) <=> string[0,10_000].count(DiacritsWin)
49
+ when -1 then return string.force_encoding(Encoding::Windows_1252)
50
+ when 1 then return string.force_encoding(Encoding::MacRoman)
51
+ end
52
+
53
+ # Bias for Windows_1252
54
+ string.force_encoding(Encoding::Windows_1252)
55
+ end
56
+
57
+ def guess_encoding(string)
58
+ force_guessed_encoding!(string.dup).encoding
59
+ end
60
+
61
+ def guess_csv_delimiter(csv, out_of=[',',';'])
62
+ out_of = out_of.map { |delimiter| delimiter.encode(csv.encoding) }
63
+
64
+ out_of.max_by { |delimiter| csv[0, 10_000].count(delimiter) }
65
+ end
66
+
67
+ def file_type_from_path(path)
68
+ case path
69
+ when /\.csv$/ then :csv
70
+ when /\.xls$/ then :xls
71
+ when /\.xlsx$/ then :xlsx
72
+ else raise InvalidFileType, "Unknown file format for path #{path.inspect}"
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,17 @@
1
+ # encoding: utf-8
2
+
3
+ module TableData
4
+ module Exception
5
+ end
6
+ class InvalidFileType < ArgumentError
7
+ include Exception
8
+ end
9
+ class InvalidColumnCount < ArgumentError
10
+ include Exception
11
+ end
12
+ class InvalidColumnSpecifier < ArgumentError
13
+ include Exception
14
+ end
15
+ class InvalidColumnName < InvalidColumnSpecifier; end
16
+ class InvalidColumnAccessor < InvalidColumnSpecifier; end
17
+ end
@@ -0,0 +1,64 @@
1
+ # encoding: utf-8
2
+
3
+ require 'tabledata/detection'
4
+ require 'tabledata/table'
5
+ require 'stringio'
6
+
7
+ module TableData
8
+ module Parser
9
+ module_function
10
+
11
+ def parse_csv(file, options=nil)
12
+ TableData.require_library 'csv', "To parse CSV files, the gem 'csv' must be installed." # Should not really happen, in 1.9, csv is part of stdlib and should be present
13
+
14
+ table_class = (options && options[:table_class]) || Table
15
+ table = table_class.new([], options)
16
+ data = read_file(file, options && options[:encoding])
17
+ seperator = (options && options[:separator]) || Detection.guess_csv_delimiter(data)
18
+ CSV.parse(data,col_sep: seperator) do |row|
19
+ table << row
20
+ end
21
+
22
+ table
23
+ end
24
+
25
+ def parse_xls(file, options=nil)
26
+ TableData.require_library 'roo', "To parse Excel .xls files, the gem 'roo' must be installed." # TODO: get rid of that dependency
27
+ TableData.require_library 'iconv', "To parse Excel .xls files, the gem 'iconv' must be installed." # TODO: get rid of that dependency
28
+
29
+ table_class = (options && options[:table_class]) || Table
30
+ table = table_class.new([], options)
31
+ parser = Roo::Excel.new(file)
32
+ parser.first_row.upto(parser.last_row) do |row|
33
+ table << parser.row(row)
34
+ end
35
+
36
+ table
37
+ end
38
+
39
+ def parse_xlsx(file, options=nil)
40
+ TableData.require_library 'roo', "To parse Excel .xlsx files, the gem 'roo' must be installed." # TODO: get rid of that dependency
41
+
42
+ table_class = (options && options[:table_class]) || Table
43
+ table = table_class.new([], options)
44
+ parser = Roo::Excelx.new(file)
45
+ parser.first_row.upto(parser.last_row) do |row|
46
+ table << parser.row(row)
47
+ end
48
+
49
+ table
50
+ end
51
+
52
+ def read_file(path, encoding)
53
+ if encoding then
54
+ File.read(path, encoding: encoding)
55
+ else
56
+ data = File.read(path, encoding: Encoding::BINARY)
57
+ Detection.force_guessed_encoding!(data)
58
+ data.encode!(Encoding.default_internal) if Encoding.default_internal
59
+
60
+ data
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,14 @@
1
+ # encoding: utf-8
2
+
3
+
4
+
5
+ require 'spreadsheet'
6
+ require 'stringio'
7
+
8
+
9
+
10
+ class Spreadsheet::Workbook
11
+ def to_string
12
+ StringIO.new.tap { |string_io| write(string_io) }.string
13
+ end
14
+ end
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+
3
+ module TableData
4
+ class Presenter
5
+ @presenters = {
6
+ csv: ['tabledata/presenters/csv', [:TableData, :Presenters, :CSV], {}],
7
+ excel_csv: ['tabledata/presenters/csv', [:TableData, :Presenters, :CSV], {column_separator: ";", row_separator: "\r\n"}],
8
+ tab: ['tabledata/presenters/csv', [:TableData, :Presenters, :CSV], {column_separator: "\t"}],
9
+ xls: ['tabledata/presenters/excel', [:TableData, :Presenters, :Excel], {suffix: '.xls'}],
10
+ xlsx: ['tabledata/presenters/excel', [:TableData, :Presenters, :Excel], {suffix: '.xlsx'}],
11
+ html: ['tabledata/presenters/html', [:TableData, :Presenters, :HTML], {}],
12
+ pdf: ['tabledata/presenters/pdf', [:TableData, :Presenters, :PDF], {}],
13
+ }
14
+
15
+ def self.present(table, format, options)
16
+ code, constant, default_options = *@presenters[format]
17
+ raise ArgumentError, "Unknown format #{format.inspect}" unless code
18
+ require code
19
+ klass = constant.inject(Object) { |source, current| source.const_get(current) }
20
+
21
+ klass.new(table, options ? default_options.merge(options) : default_options.dup)
22
+ end
23
+
24
+ attr_reader :table
25
+
26
+ def initialize(table, options)
27
+ @table = table
28
+ @options = options
29
+ end
30
+
31
+ def write(path, options=nil)
32
+ File.write(path, string, encoding: 'utf-8')
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,39 @@
1
+ # encoding: utf-8
2
+
3
+ require 'tabledata/presenter'
4
+ require 'csv'
5
+
6
+ module TableData
7
+ module Presenters
8
+ class CSV < TableData::Presenter
9
+ OptionMapping = {
10
+ column_separator: :col_sep,
11
+ row_separator: :row_sep,
12
+ quote_char: :quote_character,
13
+ }
14
+
15
+ def csv_options
16
+ options = ::CSV::DEFAULT_OPTIONS.dup
17
+ @options.each do |k,v| options[OptionMapping.fetch(k,k)] = v end
18
+
19
+ options
20
+ end
21
+
22
+ def string(options=nil)
23
+ ::CSV.generate(csv_options) do |csv|
24
+ @table.each_row do |row|
25
+ csv << row.to_a
26
+ end
27
+ end
28
+ end
29
+
30
+ def write(path, options=nil)
31
+ ::CSV.open(path, 'wb', csv_options) do |csv|
32
+ @table.each_row do |row|
33
+ csv << row.to_a
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,36 @@
1
+ # encoding: utf-8
2
+
3
+ require 'tabledata/presenter'
4
+ TableData.require_library 'spreadsheet', "To generate Excel files, the gem 'spreadsheet' must be installed."
5
+ require 'tabledata/patches/spreadsheet'
6
+
7
+
8
+ module TableData
9
+ module Presenters
10
+ class Excel < TableData::Presenter
11
+ Bold = Spreadsheet::Format.new weight: :bold
12
+
13
+ def document
14
+ document = Spreadsheet::Workbook.new
15
+ sheet = document.create_worksheet(name: @options[:worksheet_name])
16
+ sheet.row(0).default_format = Bold if @options[:bold_headers]
17
+
18
+ @table.data.each_with_index do |row, row_nr|
19
+ row.each_with_index do |col, col_nr|
20
+ sheet[row_nr, col_nr] = col
21
+ end
22
+ end
23
+
24
+ document
25
+ end
26
+
27
+ def string(options=nil)
28
+ document.to_string
29
+ end
30
+
31
+ def write(path, options=nil)
32
+ document.write(path)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,56 @@
1
+ # encoding: utf-8
2
+
3
+ require 'tabledata/presenter'
4
+ require 'cgi'
5
+
6
+ module TableData
7
+ module Presenters
8
+ class HTML < TableData::Presenter
9
+ def html_head
10
+ <<-EOHTML
11
+ <!DOCTYPE html>
12
+ <html>
13
+ <head>
14
+ <meta charset='utf-8'>
15
+ </head>
16
+ <body>
17
+ <table>
18
+ EOHTML
19
+ end
20
+
21
+ def html_foot
22
+ <<-EOHTML
23
+
24
+ </tbody>
25
+ </table>
26
+ </body>
27
+ </html>
28
+ EOHTML
29
+ end
30
+
31
+ def html_table_header
32
+ if @table.headers?
33
+ " <thead>\n <tr>\n"+
34
+ @table.headers.map { |cell|" <th>#{CGI.escapeHTML(cell)}</th>" }.join("\n")+
35
+ "\n </tr>\n </thead>\n"
36
+ else
37
+ ''
38
+ end
39
+ end
40
+
41
+ def string(options=nil)
42
+ html_head+
43
+ html_table_header+
44
+ " </body>\n"+
45
+ @table.body.map { |row|
46
+ " <tr>\n"+row.map { |cell| " <td>#{CGI.escapeHTML(cell)}</td>" }.join("\n")+"\n </tr>"
47
+ }.join("\n")+
48
+ html_foot
49
+ end
50
+
51
+ def write(path, options=nil)
52
+ File.write(path, string, encoding: 'utf-8')
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,27 @@
1
+ # encoding: utf-8
2
+
3
+ require 'tabledata/presenter'
4
+ TableData.require_library 'prawn', "To generate PDF files, the gem 'prawn' must be installed."
5
+
6
+
7
+ module TableData
8
+ module Presenters
9
+ class PDF < TableData::Presenter
10
+
11
+ def document
12
+ pdf = Prawn::Document.new
13
+ pdf.table @table.data
14
+
15
+ pdf
16
+ end
17
+
18
+ def string(options=nil)
19
+ document.render
20
+ end
21
+
22
+ def write(path, options=nil)
23
+ document.render_file(path)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,109 @@
1
+ # encoding: utf-8
2
+
3
+ require 'tabledata/exceptions'
4
+
5
+ module TableData
6
+ class Row
7
+ attr_reader :table
8
+ attr_reader :index
9
+ attr_reader :data
10
+
11
+ include Enumerable
12
+
13
+ def initialize(table, index, data)
14
+ @table = table
15
+ @index = index
16
+ @data = data
17
+ end
18
+
19
+ # Iterate over each cell in this row
20
+ def each(&block)
21
+ @data.each(&block)
22
+ end
23
+
24
+ # @see #slice for a faster way to use ranges or offset+length
25
+ # @see #at_accessor for a faster way to access by name
26
+ # @see #at_index for a faster way to access by index
27
+ # @see #at_header for a faster way to access by header value
28
+ def [](a,b=nil)
29
+ if b || a.is_a?(Range) then
30
+ slice(a,b)
31
+ else
32
+ at(a)
33
+ end
34
+ end
35
+
36
+ def slice(*args)
37
+ @data[*args]
38
+ end
39
+
40
+ def at(column)
41
+ case column
42
+ when Symbol then at_accessor(column)
43
+ when String then at_header(column)
44
+ when Integer then at_index(column)
45
+ else raise InvalidColumnSpecifier, "Invalid index type, expected Symbol, String or Integer, but got #{column.class}"
46
+ end
47
+ end
48
+
49
+ def at_header(name)
50
+ index = @table.index_for_header(name)
51
+ raise InvalidColumnName, "No column named #{name}" unless index
52
+
53
+ @data[index]
54
+ end
55
+
56
+ def at_accessor(name)
57
+ index = @table.index_for_accessor(name)
58
+ raise InvalidColumnAccessor, "No column named #{name}" unless index
59
+
60
+ @data[index]
61
+ end
62
+
63
+ def at_index(index)
64
+ @data.at(index)
65
+ end
66
+
67
+ def values_at(*columns)
68
+ columns.map { |column| at(column) }
69
+ end
70
+
71
+ # @return [Integer] The number of cells in this row
72
+ def size
73
+ @data.size
74
+ end
75
+
76
+ def to_hash
77
+ Hash[@table.accessor_columns.map { |accessor, index| [accessor, @data[index]] }]
78
+ end
79
+
80
+ alias to_a data
81
+
82
+ def respond_to_missing?(name, include_private)
83
+ @table.index_for_accessor(name) ? true : false
84
+ end
85
+
86
+ def method_missing(name, *args, &block)
87
+ return super unless @table.accessors?
88
+
89
+ name =~ /^(\w+)(=)?$/
90
+ name_mod, assign = $1, $2
91
+ index = @table.index_for_accessor(name_mod)
92
+ arg_count = assign ? 1 : 0
93
+
94
+ return super unless index
95
+
96
+ raise ArgumentError, "Wrong number of arguments (#{args.size} for #{arg_count})" if args.size > arg_count
97
+
98
+ if assign then
99
+ @data[index] = args.first
100
+ else
101
+ @data[index]
102
+ end
103
+ end
104
+
105
+ def inspect
106
+ sprintf "%s%p", self.class, to_a
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,250 @@
1
+ # encoding: utf-8
2
+
3
+ require 'tabledata/parser'
4
+ require 'tabledata/row'
5
+ require 'tabledata/column'
6
+ require 'tabledata/detection'
7
+ require 'tabledata/exceptions'
8
+ require 'tabledata/presenter'
9
+
10
+ module TableData
11
+
12
+ # This class represents the tabular data.
13
+ class Table
14
+
15
+ include Enumerable
16
+
17
+ # The default options for TableData::Table#initialize
18
+ DefaultOptions = {
19
+ has_header: true,
20
+ has_footer: false, # currently unused
21
+ accessors: [],
22
+ }
23
+
24
+ # @option options [Symbol] :file_type
25
+ # The file type. Nil for auto-detection (which uses the extension of the
26
+ # filename), or one of :csv, :xls or :xlsx
27
+ # @option options [Symbol] :table_class
28
+ # The class to use for this table. Defaults to self (TableData::Table)
29
+ #
30
+ # All other options are passed on to Parser.parse_csv, .parse_xls or parse_xlsx,
31
+ # which in turn passes remaining options on to Table#initialize
32
+ #
33
+ # @return [TableData::Table]
34
+ def self.from_file(path, options=nil)
35
+ options ||= {}
36
+ options[:table_class] ||= self
37
+ options[:file_type] ||= Detection.file_type_from_path(path)
38
+
39
+ case options[:file_type]
40
+ when :csv then Parser.parse_csv(path, options)
41
+ when :xls then Parser.parse_xls(path, options)
42
+ when :xlsx then Parser.parse_xlsx(path, options)
43
+ else raise InvalidFileType, "Unknown file format #{options[:file_type].inspect}"
44
+ end
45
+ end
46
+
47
+ # @return [Array<Symbol>] An array of all named accessors
48
+ attr_reader :accessors
49
+
50
+ # @return [Hash<Symbol => Integer>] A hash mapping column accessor names to the column index
51
+ attr_reader :accessor_columns
52
+
53
+ # @private
54
+ # The internal data structure. Do not modify.
55
+ attr_reader :data
56
+
57
+ def initialize(data=[], options=nil)
58
+ options = options ? self.class::DefaultOptions.merge(options) : self.class::DefaultOptions.dup
59
+ column_count = data.first ? data.first.size : 0
60
+ @has_header = options.delete(:has_header) ? true : false
61
+ @data = data
62
+ @rows = data.map.with_index { |row, index|
63
+ raise InvalidColumnCount, "Invalid column count in row #{index} (#{column_count} expected, but has #{row.size})" if index > 0 && row.size != column_count
64
+ raise ArgumentError, "Row must be provided as Array, but got #{row.class} in row #{index}" unless row.is_a?(Array)
65
+
66
+ Row.new(self, index, row)
67
+ }
68
+ @column_count = nil
69
+ @header_columns = nil
70
+ @accessor_columns = {}
71
+ @column_accessors = {}
72
+ @accessors = [].freeze
73
+ self.accessors = options.delete(:accessors)
74
+ end
75
+
76
+ # @param [Array<Symbol>] accessors
77
+ #
78
+ # Define the name of the accessors used in TableData::Row.
79
+ def accessors=(accessors)
80
+ if accessors
81
+ @accessors = accessors.map(&:to_sym).freeze
82
+ @accessors.each_with_index do |name, idx|
83
+ @accessor_columns[name] = idx
84
+ end
85
+ @column_accessors = @accessor_columns.invert
86
+ else
87
+ @accessors = [].freeze
88
+ @accessor_columns.clear
89
+ @column_accessors = @accessor_columns.clear
90
+ end
91
+ end
92
+
93
+ # The number of rows, excluding headers
94
+ def size
95
+ @data.size - (@has_header ? 1 : 0)
96
+ end
97
+ alias length size
98
+
99
+ # @return [Integer] The number of columns
100
+ def column_count
101
+ @data.first ? @data.first.size : 0
102
+ end
103
+
104
+ # Array#[] like access to the rows in the body of the table.
105
+ #
106
+ # @return [Array<TableData::Row>]
107
+ def [](*args)
108
+ body[*args]
109
+ end
110
+
111
+ def cell(row, column, default=nil)
112
+ row_data = row(row)
113
+
114
+ if row_data
115
+ row_data.at(column)
116
+ elsif block_given?
117
+ yield(self, row, column)
118
+ else
119
+ default
120
+ end
121
+ end
122
+
123
+ def row(row)
124
+ @rows[row]
125
+ end
126
+
127
+ def column_accessor(index)
128
+ @column_accessors[index]
129
+ end
130
+
131
+ def column_name(index)
132
+ h = headers
133
+
134
+ h && h.at(index)
135
+ end
136
+
137
+ def columns
138
+ Array.new(column_count) { |col| column(col) }
139
+ end
140
+
141
+ def column(index)
142
+ Column.new(self, index)
143
+ end
144
+
145
+ def index_for_accessor(name)
146
+ @accessor_columns[name.to_sym]
147
+ end
148
+
149
+ def index_for_header(name)
150
+ if @has_header && @data.first then
151
+ @header_columns ||= Hash[@data.first.each_with_index.to_a]
152
+ @header_columns[name]
153
+ else
154
+ nil
155
+ end
156
+ end
157
+
158
+ def accessors?
159
+ !@accessors.empty?
160
+ end
161
+
162
+ def headers?
163
+ @has_header
164
+ end
165
+
166
+ def headers
167
+ headers? ? @rows.first : nil
168
+ end
169
+
170
+ def body
171
+ headers? ? @rows[1..-1] : @rows
172
+ end
173
+
174
+ def <<(row)
175
+ index = @data.size
176
+
177
+ raise InvalidColumnCount, "Invalid column count in row #{index} (#{@data.first.size} expected, but has #{row.size})" if @data.first && row.size != @data.first.size
178
+ raise ArgumentError, "Row must be provided as Array, but got #{row.class} in row #{index}" unless row.is_a?(Array)
179
+
180
+ @data << row
181
+ @rows << Row.new(self, index, row)
182
+
183
+ self
184
+ end
185
+
186
+ # Iterate over all rows in the body
187
+ #
188
+ # @see TableData::Table#each_row A method which iterates over all rows, including headers
189
+ #
190
+ # @yield [row]
191
+ # @yieldparam [TableData::Row]
192
+ #
193
+ # @return [self]
194
+ def each(&block)
195
+ return enum_for(__method__) unless block
196
+
197
+ body.each(&block)
198
+
199
+ self
200
+ end
201
+
202
+ # Iterate over all rows, header and body
203
+ #
204
+ # @see TableData::Table#each A method which iterates only over body-rows
205
+ #
206
+ # @yield [row]
207
+ # @yieldparam [TableData::Row]
208
+ #
209
+ # @return [self]
210
+ def each_row(&block)
211
+ return enum_for(__method__) unless block
212
+
213
+ @data.each(&block)
214
+
215
+ self
216
+ end
217
+
218
+ # Iterate over all columns
219
+ #
220
+ # @yield [column]
221
+ # @yieldparam [TableData::Column]
222
+ #
223
+ # @return [self]
224
+ def each_column
225
+ return enum_for(__method__) unless block
226
+
227
+ column_count.times do |i|
228
+ yield column(i)
229
+ end
230
+
231
+ self
232
+ end
233
+
234
+ def to_nested_array
235
+ to_a.map(&:to_a)
236
+ end
237
+
238
+ def to_a
239
+ @data
240
+ end
241
+
242
+ def format(format_id, options=nil)
243
+ Presenter.present(self, format_id, options)
244
+ end
245
+
246
+ def inspect
247
+ sprintf "#<%s headers: %p, cols: %d, rows: %d>", self.class, headers?, column_count, size
248
+ end
249
+ end
250
+ end
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+
3
+ begin
4
+ require 'rubygems/version' # newer rubygems use this
5
+ rescue LoadError
6
+ require 'gem/version' # older rubygems use this
7
+ end
8
+
9
+ module TableData
10
+ Version = Gem::Version.new("0.0.3")
11
+ end
@@ -0,0 +1,43 @@
1
+ # encoding: utf-8
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "tabledata"
5
+ s.version = "0.0.3"
6
+ s.authors = "Stefan Rusterholz"
7
+ s.email = "stefan.rusterholz@gmail.com"
8
+ s.homepage = "https://github.com/apeiros/tabledata"
9
+ s.license = 'BSD 2-Clause'
10
+
11
+ s.description = <<-DESCRIPTION.gsub(/^ /, '').chomp
12
+ Read and write tabular data from and to various formats.
13
+ DESCRIPTION
14
+ s.summary = <<-SUMMARY.gsub(/^ /, '').chomp
15
+ Read and write tabular data from and to various formats.
16
+ SUMMARY
17
+
18
+ s.files =
19
+ Dir['bin/**/*'] +
20
+ Dir['lib/**/*'] +
21
+ Dir['rake/**/*'] +
22
+ Dir['test/**/*'] +
23
+ Dir['*.gemspec'] +
24
+ %w[
25
+ LICENSE.txt
26
+ Rakefile
27
+ README.markdown
28
+ ]
29
+
30
+ if File.directory?('bin') then
31
+ s.executables = Dir.chdir('bin') { Dir.glob('**/*').select { |f| File.executable?(f) } }
32
+ end
33
+
34
+ s.add_dependency 'spreadsheet', '>= 0.8.5'
35
+ s.add_dependency 'prawn', '>= 0.12.0'
36
+ s.add_dependency 'roo', '>= 1.11.2'
37
+ s.add_dependency 'iconv', '>= 1.0.3'
38
+
39
+ s.required_ruby_version = ">= 1.9.2"
40
+ s.rubygems_version = "1.3.1"
41
+ s.specification_version = 3
42
+ s.required_rubygems_version = Gem::Requirement.new("> 1.3.1")
43
+ end
Binary file
Binary file
@@ -0,0 +1,4 @@
1
+ cola,colb,colc,cold
2
+ 1,2,3,4
3
+ 10,20,30,40
4
+ hägar,frühstück,öl,sonstwas
@@ -0,0 +1,4 @@
1
+ cola;colb;colc;cold
2
+ 1;2;3;4
3
+ 10;20;30;40
4
+ hägar;frühstück;öl;sonstwas
@@ -0,0 +1,4 @@
1
+ cola,colb,colc,cold
2
+ 1,2,3,4
3
+ 10,20,30,40
4
+ h�gar,fr�hst�ck,�l,sonstwas
@@ -0,0 +1,4 @@
1
+ cola;colb;colc;cold
2
+ 1;2;3;4
3
+ 10;20;30;40
4
+ h�gar;fr�hst�ck;�l;sonstwas
File without changes
metadata ADDED
@@ -0,0 +1,127 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tabledata
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - Stefan Rusterholz
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-07-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: spreadsheet
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: 0.8.5
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: 0.8.5
27
+ - !ruby/object:Gem::Dependency
28
+ name: prawn
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: 0.12.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: 0.12.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: roo
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: 1.11.2
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: 1.11.2
55
+ - !ruby/object:Gem::Dependency
56
+ name: iconv
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: 1.0.3
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: 1.0.3
69
+ description: Read and write tabular data from and to various formats.
70
+ email: stefan.rusterholz@gmail.com
71
+ executables: []
72
+ extensions: []
73
+ extra_rdoc_files: []
74
+ files:
75
+ - lib/tabledata/coercing_row.rb
76
+ - lib/tabledata/coercion.rb
77
+ - lib/tabledata/column.rb
78
+ - lib/tabledata/detection.rb
79
+ - lib/tabledata/exceptions.rb
80
+ - lib/tabledata/parser.rb
81
+ - lib/tabledata/patches/spreadsheet.rb
82
+ - lib/tabledata/presenter.rb
83
+ - lib/tabledata/presenters/csv.rb
84
+ - lib/tabledata/presenters/excel.rb
85
+ - lib/tabledata/presenters/html.rb
86
+ - lib/tabledata/presenters/pdf.rb
87
+ - lib/tabledata/row.rb
88
+ - lib/tabledata/table.rb
89
+ - lib/tabledata/version.rb
90
+ - lib/tabledata.rb
91
+ - test/data/test1.xls
92
+ - test/data/test1.xlsx
93
+ - test/data/test_csv_utf8_comma_n.csv
94
+ - test/data/test_csv_utf8_semicolon_n.csv
95
+ - test/data/test_csv_win1252_comma_rn.csv
96
+ - test/data/test_csv_win1252_semicolon_rn.csv
97
+ - test/unit/all.rb
98
+ - tabledata.gemspec
99
+ - LICENSE.txt
100
+ - Rakefile
101
+ - README.markdown
102
+ homepage: https://github.com/apeiros/tabledata
103
+ licenses:
104
+ - BSD 2-Clause
105
+ metadata: {}
106
+ post_install_message:
107
+ rdoc_options: []
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - '>='
113
+ - !ruby/object:Gem::Version
114
+ version: 1.9.2
115
+ required_rubygems_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - '>'
118
+ - !ruby/object:Gem::Version
119
+ version: 1.3.1
120
+ requirements: []
121
+ rubyforge_project:
122
+ rubygems_version: 2.0.3
123
+ signing_key:
124
+ specification_version: 3
125
+ summary: Read and write tabular data from and to various formats.
126
+ test_files: []
127
+ has_rdoc: