bayonet 0.2.0

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: 2bec54b0bad049f5802aca27083b33bc782290dc
4
+ data.tar.gz: 5ee3c94ac1f7ddd9b70675af7109b6952efed880
5
+ SHA512:
6
+ metadata.gz: fcd56d9e58e861a38fb7976997f6487637c8f3e46a9d1c3426fd3cba5abfc8c74c284115cac3b8d5c06629fc328b7acb260749a1c31dfcdbf9e43e9c0d36ec37
7
+ data.tar.gz: 193a3fe91b11c9baa8adad8fd5990b548eb290002e3b72dddc30c7dfb39ec55ed788d77c918c621cd4a05f05876f4bc0bf97d1f776e51402c54ed708e83619cc
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1 @@
1
+ 2.2.2
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.0
4
+
5
+ script: bundle exec rspec
6
+
7
+ addons:
8
+ code_climate:
9
+ repo_token: bf3282e65bbe4a73a660110de614caad3f6e65449563b61666088a877d5661d5
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :test do
4
+ gem 'rspec', '~> 3.2.0'
5
+ gem 'roo', '~> 2.0'
6
+ gem 'byebug'
7
+ gem 'codeclimate-test-reporter'
8
+ end
9
+
10
+ # Specify your gem's dependencies in bayonet.gemspec
11
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Thomas Ritter
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,58 @@
1
+ # Bayonet gem
2
+
3
+ [![Build Status](https://travis-ci.org/nethad/bayonet.svg?branch=master)](https://travis-ci.org/nethad/bayonet)
4
+ [![Code Climate](https://codeclimate.com/github/nethad/bayonet/badges/gpa.svg)](https://codeclimate.com/github/nethad/bayonet)
5
+ [![Test Coverage](https://codeclimate.com/github/nethad/bayonet/badges/coverage.svg)](https://codeclimate.com/github/nethad/bayonet/coverage)
6
+
7
+ Bayonet is a Microsoft Excel write-only gem that reads and produces XLSX files.
8
+ It's strength lies in the fact that it's able to open bigger Excel files (even with macros!) -- and write cells without touching the rest of the Excel file. I've written the gem because *roo* was unbearably slow and was using ~1GB of RAM to read a 5MB XLSX file.
9
+
10
+
11
+ **WARNING:** Use this gem at your own risk. It's writing directly to sheet XML files and uses a few tricks that Microsoft Excel 2010 seems to be fine with, but I cannot guarantee that future (or past) versions will play along. That being said, I've used it successfully in one of my projects. If you find any bugs, let me know.
12
+
13
+ ### Usage example
14
+
15
+ ```ruby
16
+ workbook = Bayonet::Workbook.new(a_file_path)
17
+
18
+ workbook.on_sheet('The First Sheet') do |sheet|
19
+ sheet.set_typed_cell('A', 1, "I'm a string") # set a cell auto-typed to a string
20
+ sheet.set_typed_cell('B', 1, 42) # set a cell auto-typed to a number
21
+ sheet.set_cell('C', 1, "Some value") # set a cell without setting its type
22
+ sheet.write_string('D', 1, "Some value") # set a cell and forcing it to be a string
23
+ sheet.write_number('E', 1, 23) # set a cell and forcing it to be a number
24
+ end
25
+
26
+ workbook.write_and_close(output_file_path)
27
+ ```
28
+
29
+ See the *Tips* section for more information.
30
+
31
+ ### When to use Bayonet?
32
+
33
+ * If you want to modify an existing, possibly huge XLSX file.
34
+ * If other gems fail at the task or are too slow.
35
+
36
+ ### When should I use other gems?
37
+
38
+ * If you want to create an XLSX file from scratch.
39
+ * If you plan to open legacy (binary) XLS files.
40
+ * If your XLSX file is small and other gems are working fine.
41
+
42
+ Other gems I can recommend are:
43
+
44
+ * [roo](https://rubygems.org/gems/roo) (read/write, lots of features and supported formats)
45
+ * [axlsx](https://rubygems.org/gems/axlsx) (write-only)
46
+ * [creek](https://rubygems.org/gems/creek) (read-only, very fast)
47
+
48
+
49
+ ### Tips
50
+
51
+ #### When should I use `set_typed_cell` vs. `set_cell`?
52
+
53
+ Normally you should use `set_typed_cell`, which sets the cell type correctly and falls back to `string`. If you're using a prepared XLSX sheet you might not want to override the current cell type. You should use `set_cell` for those instances. If you want to force a cell type, you can use `write_string` or `write_number` instead, but this should rarely be necessary.
54
+
55
+ #### Why can't I read cells?
56
+
57
+ It's simply not been implemented yet. I've been writing to template files with pre-defined cells to fill, so reading cells was not necessary. It shouldn't be hard to implemented, though there might be some edge cases I haven't considered yet, that's why I left it out for now. Pull requests are welcome, otherwise I'd suggest to use other gems for that.
58
+
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'bayonet/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "bayonet"
8
+ spec.version = Bayonet::VERSION
9
+ spec.authors = ["Thomas Ritter"]
10
+ spec.email = ["ritter.thomas@gmail.com"]
11
+
12
+ spec.summary = %q{Bayonet is a Microsoft Excel write-only gem that reads and produces XLSX files.}
13
+ spec.description = %q{Bayonet is a Microsoft Excel write-only gem that reads and produces XLSX files. It's strength lies in the fact that it's able to open bigger Excel files (even with macros!) -- and write cells without touching the rest of the Excel file.}
14
+ spec.homepage = "https://github.com/nethad/bayonet"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.7"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency "rubyzip", "~> 1.1"
25
+ spec.add_development_dependency "nokogiri", "~> 1.6"
26
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "bayonet"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,8 @@
1
+ require "bayonet/cell"
2
+ require "bayonet/node_creation"
3
+ require "bayonet/sheet"
4
+ require "bayonet/version"
5
+ require "bayonet/workbook"
6
+
7
+ module Bayonet
8
+ end
@@ -0,0 +1,37 @@
1
+ module Bayonet
2
+ class Cell
3
+
4
+ ROW_MATCHER = /\A[A-Z]+\z/ # is only A-Z characters, upper case.
5
+
6
+ def initialize(row, column)
7
+ @row = row
8
+ @column = column
9
+ end
10
+
11
+ def label
12
+ cell_label = "#{row}#{column}"
13
+ if valid?
14
+ cell_label
15
+ else
16
+ raise "Invalid cell: #{cell_label}."
17
+ end
18
+ end
19
+
20
+ def valid?
21
+ row_valid? && column_valid?
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :row, :column
27
+
28
+ def row_valid?
29
+ (row =~ ROW_MATCHER) != nil
30
+ end
31
+
32
+ def column_valid?
33
+ column.is_a?(Integer) && column >= 1
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,79 @@
1
+ module Bayonet
2
+ class NodeCreation
3
+
4
+ attr_reader :cell_label, :sheet
5
+
6
+ def initialize(cell_label, sheet)
7
+ @cell_label = cell_label
8
+ @sheet = sheet
9
+ end
10
+
11
+ def get_or_create_cell_node
12
+ unless has_cell_node?
13
+ create_nodes
14
+ end
15
+
16
+ cell_node
17
+ end
18
+
19
+ private
20
+
21
+ def xml
22
+ sheet.xml
23
+ end
24
+
25
+ def cell_node
26
+ @cell_node ||= xml.at_css("c[r=\"#{cell_label}\"]")
27
+ end
28
+
29
+ def has_cell_node?
30
+ !cell_node.nil?
31
+ end
32
+
33
+ def row_node
34
+ @row_node ||= xml.at_css("row[r=\"#{row_number}\"]")
35
+ end
36
+
37
+ def has_row_node?
38
+ !row_node.nil?
39
+ end
40
+
41
+ def row_number
42
+ @row_number ||= cell_label.gsub(/[^\d]/, '')
43
+ end
44
+
45
+ def sheet_data_node
46
+ @sheet_data_node ||= xml.at_css('sheetData')
47
+ end
48
+
49
+ def create_cell_node
50
+ create_node('c', row_node, cell_label)
51
+ end
52
+
53
+ def create_row_node
54
+ create_node('row', sheet_data_node, row_number)
55
+ end
56
+
57
+ def create_node(tag, parent_node, value)
58
+ Nokogiri::XML::Node.new(tag, parent_node).tap do |node|
59
+ node['r'] = value
60
+ parent_node.add_child(node)
61
+ end
62
+ end
63
+
64
+ def create_row_node_with_cell_node
65
+ row_node = create_row_node
66
+ cell_node = create_cell_node
67
+ row_node.add_child(cell_node)
68
+ end
69
+
70
+ def create_nodes
71
+ if has_row_node?
72
+ create_cell_node
73
+ else
74
+ create_row_node_with_cell_node
75
+ end
76
+ end
77
+
78
+ end
79
+ end
@@ -0,0 +1,66 @@
1
+ module Bayonet
2
+ class Sheet
3
+
4
+ def initialize(name, workbook)
5
+ @name = name
6
+ @workbook = workbook
7
+ end
8
+
9
+ def path
10
+ "xl/worksheets/sheet#{id}.xml"
11
+ end
12
+
13
+ def xml
14
+ @xml ||= read_and_parse_xml
15
+ end
16
+
17
+ def write_string(cell_row, cell_column, value)
18
+ set_cell(cell_row, cell_column, value.to_s, :str)
19
+ end
20
+
21
+ def write_number(cell_row, cell_column, value)
22
+ set_cell(cell_row, cell_column, value, :n)
23
+ end
24
+
25
+ def set_cell(cell_row, cell_column, value, type = nil)
26
+ cell_node = get_or_create_cell_node(Cell.new(cell_row, cell_column))
27
+ cell_node['t'] = type unless type.nil?
28
+ set_value(cell_node, value)
29
+ end
30
+
31
+ def set_typed_cell(cell_row, cell_column, value)
32
+ if (value.is_a?(Numeric))
33
+ write_number(cell_row, cell_column, value)
34
+ else
35
+ write_string(cell_row, cell_column, value)
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ attr_reader :name, :workbook
42
+
43
+ def id
44
+ @id ||= workbook.xml.at_css("sheets sheet[name=\"#{name}\"]")["r:id"][3..-1]
45
+ end
46
+
47
+ def read_and_parse_xml
48
+ entry = workbook.zip_file.find_entry(path)
49
+ Nokogiri::XML.parse(entry.get_input_stream)
50
+ end
51
+
52
+ def get_or_create_cell_node(cell)
53
+ Bayonet::NodeCreation.new(cell.label, self).get_or_create_cell_node
54
+ end
55
+
56
+ def set_value(cell_node, value)
57
+ if cell_node.children.nil? || cell_node.children.empty?
58
+ value_node = Nokogiri::XML::Node.new('v', cell_node)
59
+ value_node.content = value
60
+ cell_node.add_child value_node
61
+ else
62
+ cell_node.at_css("v").content = value
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,3 @@
1
+ module Bayonet
2
+ VERSION = "0.2.0"
3
+ end
@@ -0,0 +1,79 @@
1
+ require 'zip'
2
+ require 'nokogiri'
3
+
4
+ module Bayonet
5
+ class Workbook
6
+
7
+ WORKBOOK_PATH = 'xl/workbook.xml'
8
+
9
+ def initialize(file)
10
+ @file = file
11
+ end
12
+
13
+ def zip_file
14
+ @zip_file ||= Zip::File.open(file)
15
+ end
16
+
17
+ def xml
18
+ @xml ||= read_and_parse_xml
19
+ end
20
+
21
+ def on_sheet(sheet_name, &block)
22
+ sheet = find_sheet_by_name_and_cache(sheet_name)
23
+ block.call(sheet)
24
+ end
25
+
26
+ def write(file)
27
+ Zip::File.open(file, Zip::File::CREATE) do |destination|
28
+ zip_file.each do |entry|
29
+ destination.get_output_stream(entry.name) do |file_stream|
30
+ write_modifications(entry, file_stream)
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ def close
37
+ zip_file.close
38
+ end
39
+
40
+ def write_and_close(file)
41
+ write(file)
42
+ close
43
+ end
44
+
45
+ private
46
+
47
+ attr_reader :file
48
+
49
+ def find_sheet_by_name_and_cache(sheet_name)
50
+ return modified_sheets[sheet_name] if modified_sheets.key?(sheet_name)
51
+
52
+ sheet = Bayonet::Sheet.new(sheet_name, self)
53
+ modified_sheets[sheet_name] = sheet
54
+ sheet
55
+ end
56
+
57
+ def find_sheet_by_path(path)
58
+ modified_sheets.values.detect { |sheet| sheet.path == path }
59
+ end
60
+
61
+ def write_modifications(entry, file_stream)
62
+ sheet = find_sheet_by_path(entry.name)
63
+ if sheet
64
+ file_stream.write(sheet.xml.to_s)
65
+ else
66
+ file_stream.write(zip_file.read(entry.name))
67
+ end
68
+ end
69
+
70
+ def modified_sheets
71
+ @modified_sheets ||= {}
72
+ end
73
+
74
+ def read_and_parse_xml
75
+ entry = zip_file.find_entry(WORKBOOK_PATH)
76
+ Nokogiri::XML.parse(entry.get_input_stream)
77
+ end
78
+ end
79
+ end
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bayonet
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Thomas Ritter
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-06-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rubyzip
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.1'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: nokogiri
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.6'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.6'
69
+ description: Bayonet is a Microsoft Excel write-only gem that reads and produces XLSX
70
+ files. It's strength lies in the fact that it's able to open bigger Excel files
71
+ (even with macros!) -- and write cells without touching the rest of the Excel file.
72
+ email:
73
+ - ritter.thomas@gmail.com
74
+ executables: []
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - ".gitignore"
79
+ - ".rspec"
80
+ - ".ruby-version"
81
+ - ".travis.yml"
82
+ - Gemfile
83
+ - LICENSE.txt
84
+ - README.md
85
+ - Rakefile
86
+ - bayonet.gemspec
87
+ - bin/console
88
+ - bin/setup
89
+ - lib/bayonet.rb
90
+ - lib/bayonet/cell.rb
91
+ - lib/bayonet/node_creation.rb
92
+ - lib/bayonet/sheet.rb
93
+ - lib/bayonet/version.rb
94
+ - lib/bayonet/workbook.rb
95
+ homepage: https://github.com/nethad/bayonet
96
+ licenses:
97
+ - MIT
98
+ metadata: {}
99
+ post_install_message:
100
+ rdoc_options: []
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ requirements: []
114
+ rubyforge_project:
115
+ rubygems_version: 2.4.5
116
+ signing_key:
117
+ specification_version: 4
118
+ summary: Bayonet is a Microsoft Excel write-only gem that reads and produces XLSX
119
+ files.
120
+ test_files: []