qbxml-dtd6 1.0.1 → 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.DS_Store +0 -0
- data/.gitignore +17 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +22 -0
- data/README.md +95 -0
- data/Rakefile +1 -0
- data/TODO.md +3 -0
- data/lib/qbxml/hash.rb +149 -0
- data/lib/qbxml/qbxml.rb +102 -0
- data/lib/qbxml/types.rb +39 -0
- data/lib/qbxml/version.rb +3 -0
- data/lib/qbxml.rb +12 -0
- data/qbxml-dtd6.gemspec +23 -0
- data/schema/qbposxmlops30.xml +8343 -0
- data/schema/qbxmlops70.xml +26714 -0
- data/spec/backwards_compatibility.rb +26 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/support/requests/account_query_rq.xml +8 -0
- data/spec/support/requests/customer_add_rq.xml +28 -0
- data/spec/support/requests/customer_query_iterator_rq.xml +11 -0
- data/spec/support/requests/customer_query_rq.xml +9 -0
- data/spec/support/requests/estimate_add_rq.xml +45 -0
- data/spec/support/requests/invoice_add_rq.xml +40 -0
- data/spec/support/requests/item_inventory_add_rq.xml +22 -0
- data/spec/support/requests/purchase_order_query_rq.xml +14 -0
- data/spec/support/requests/receive_payment_add_rq.xml +35 -0
- data/spec/support/requests/receive_payment_query_rq.xml +9 -0
- data/spec/support/requests/sales_receipt_add_rq.xml +49 -0
- data/spec/support/responses/account_query_rs.xml +42 -0
- data/spec/support/responses/customer_add_rs.xml +37 -0
- data/spec/support/responses/customer_query_rs.xml +49 -0
- data/spec/support/responses/customer_query_terator_rs.xml +34 -0
- data/spec/support/responses/item_inventory_add_rs.xml +40 -0
- data/spec/support/responses/purchase_order_query_rs.xml +84 -0
- data/spec/support/responses/receive_payment_query_rs.xml +51 -0
- data/spec/support/responses/sales_receipt_add_rs.xml +89 -0
- metadata +60 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4d3cb8d7042ebeea93b31ddd770224a98445700b
|
4
|
+
data.tar.gz: e40f5b7753319f7884a7edff2fc89b5c6da36a95
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3484a3475ed742cb614c9777ea13fef16d4391127fc16c07c7214b475148e8a2570dd6b8c917edbb3d910ae033f2e76f17b7efed2ef975ca0a749348b129c4b2
|
7
|
+
data.tar.gz: cb65ca4a0ab52d643ab725b8d30166aa38d92fb877406cc4e32a3a40550f2b73d675b5f83a53c7b3778641c3f5b315b80ae85108dbd0c9c8c4c2523bfb59b0e7
|
data/.DS_Store
ADDED
Binary file
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Alex Skryl
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
# Qbxml
|
2
|
+
|
3
|
+
Qbxml is a QBXML parser and validation tool.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'qbxml'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install qbxml
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
### Initialization
|
22
|
+
|
23
|
+
The parser can be initialized to either Quickbooks (:qb) or Quickbooks Point of
|
24
|
+
Sale (:qbpos)
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
q = Qbxml.new(:qb)
|
28
|
+
```
|
29
|
+
|
30
|
+
### API Introspection
|
31
|
+
|
32
|
+
Return all types defined in the schema
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
q.types
|
36
|
+
```
|
37
|
+
|
38
|
+
Return all types matching a certain pattern
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
q.types('Customer')
|
42
|
+
|
43
|
+
q.types(/Customer/)
|
44
|
+
```
|
45
|
+
|
46
|
+
Print the xml template for a specific type
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
puts q.describe('CustomerModRq')
|
50
|
+
```
|
51
|
+
|
52
|
+
### QBXML To Ruby
|
53
|
+
|
54
|
+
Convert valid QBXML to a ruby hash
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
q.from_qbxml(xml)
|
58
|
+
```
|
59
|
+
|
60
|
+
### Ruby To QBXML
|
61
|
+
|
62
|
+
Convert a ruby hash to QBXML, skipping validation
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
q.to_qbxml(hsh)
|
66
|
+
```
|
67
|
+
|
68
|
+
Convert a ruby hash to QBXML and validate all types
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
q.to_qbxml(hsh, validate: true)
|
72
|
+
```
|
73
|
+
|
74
|
+
## Caveats
|
75
|
+
|
76
|
+
Correct case conversion depends on the following ActiveSupport inflection
|
77
|
+
settings. Correct behaviour cannot be guaranteed if any of the following
|
78
|
+
inflections are modified.
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
ACRONYMS = ['AP', 'AR', 'COGS', 'COM', 'UOM', 'QBXML', 'UI', 'AVS', 'ID',
|
82
|
+
'PIN', 'SSN', 'COM', 'CLSID', 'FOB', 'EIN', 'UOM', 'PO', 'PIN', 'QB']
|
83
|
+
|
84
|
+
ActiveSupport::Inflector.inflections do |inflect|
|
85
|
+
ACRONYMS.each { |a| inflect.acronym a }
|
86
|
+
end
|
87
|
+
```
|
88
|
+
|
89
|
+
## Contributing
|
90
|
+
|
91
|
+
1. Fork it
|
92
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
93
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
94
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
95
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/TODO.md
ADDED
data/lib/qbxml/hash.rb
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
# XML Conversion References
|
2
|
+
#
|
3
|
+
# https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/hash/conversions.rb
|
4
|
+
# https://github.com/rails/rails/blob/master/activesupport/lib/active_support/xml_mini/nokogiri.rb
|
5
|
+
#
|
6
|
+
#
|
7
|
+
class Qbxml::Hash < ::Hash
|
8
|
+
include Qbxml::Types
|
9
|
+
|
10
|
+
CONTENT_ROOT = '__content__'.freeze
|
11
|
+
ATTR_ROOT = 'xml_attributes'.freeze
|
12
|
+
IGNORED_KEYS = [ATTR_ROOT]
|
13
|
+
|
14
|
+
|
15
|
+
def self.from_hash(hash, opts = {}, &block)
|
16
|
+
key_proc = \
|
17
|
+
if opts[:camelize]
|
18
|
+
lambda { |k| k.camelize }
|
19
|
+
elsif opts[:underscore]
|
20
|
+
lambda { |k| k.underscore }
|
21
|
+
end
|
22
|
+
|
23
|
+
deep_convert(hash, opts, &key_proc)
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_xml(opts = {})
|
27
|
+
hash = self.class.to_xml(self, opts)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.to_xml(hash, opts = {})
|
31
|
+
opts[:root], hash = hash.first
|
32
|
+
opts[:attributes] = hash.delete(ATTR_ROOT)
|
33
|
+
hash_to_xml(hash, opts)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.from_xml(xml, opts = {})
|
37
|
+
from_hash(
|
38
|
+
xml_to_hash(Nokogiri::XML(xml).root, {}, opts), opts)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def self.hash_to_xml(hash, opts = {})
|
44
|
+
opts = opts.dup
|
45
|
+
opts[:indent] ||= 2
|
46
|
+
opts[:root] ||= :hash
|
47
|
+
opts[:attributes] ||= (hash.delete(ATTR_ROOT) || {})
|
48
|
+
opts[:xml_directive] ||= [:xml, {}]
|
49
|
+
opts[:builder] ||= Builder::XmlMarkup.new(indent: opts[:indent])
|
50
|
+
opts[:skip_types] = true unless opts.key?(:skip_types)
|
51
|
+
opts[:skip_instruct] = false unless opts.key?(:skip_instruct)
|
52
|
+
builder = opts[:builder]
|
53
|
+
|
54
|
+
unless opts.delete(:skip_instruct)
|
55
|
+
builder.instruct!(opts[:xml_directive].first, opts[:xml_directive].last)
|
56
|
+
end
|
57
|
+
|
58
|
+
builder.tag!(opts[:root], opts.delete(:attributes)) do
|
59
|
+
hash.each do |key, val|
|
60
|
+
case val
|
61
|
+
when Hash
|
62
|
+
self.hash_to_xml(val, opts.merge({root: key, skip_instruct: true}))
|
63
|
+
when Array
|
64
|
+
val.map { |i| self.hash_to_xml(i, opts.merge({root: key, skip_instruct: true})) }
|
65
|
+
else
|
66
|
+
builder.tag!(key, val, {})
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
yield builder if block_given?
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.xml_to_hash(node, hash = {}, opts = {})
|
75
|
+
node_hash = {CONTENT_ROOT => '', ATTR_ROOT => {}}
|
76
|
+
name = node.name
|
77
|
+
schema = opts[:schema]
|
78
|
+
|
79
|
+
# Insert node hash into parent hash correctly.
|
80
|
+
case hash[name]
|
81
|
+
when Array then hash[name] << node_hash
|
82
|
+
when Hash then hash[name] = [hash[name], node_hash]
|
83
|
+
else hash[name] = node_hash
|
84
|
+
end
|
85
|
+
|
86
|
+
# Handle child elements
|
87
|
+
node.children.each do |c|
|
88
|
+
if c.element?
|
89
|
+
xml_to_hash(c, node_hash, opts)
|
90
|
+
elsif c.text? || c.cdata?
|
91
|
+
node_hash[CONTENT_ROOT] << c.content
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Handle attributes
|
96
|
+
node.attribute_nodes.each { |a| node_hash[ATTR_ROOT][a.node_name] = a.value }
|
97
|
+
|
98
|
+
# TODO: Strip text
|
99
|
+
# node_hash[CONTENT_ROOT].strip!
|
100
|
+
|
101
|
+
# Format node
|
102
|
+
if node_hash.size > 2 || node_hash[ATTR_ROOT].present?
|
103
|
+
node_hash.delete(CONTENT_ROOT)
|
104
|
+
elsif node_hash[CONTENT_ROOT].present?
|
105
|
+
node_hash.delete(ATTR_ROOT)
|
106
|
+
hash[name] = \
|
107
|
+
if schema
|
108
|
+
typecast(schema, node.path, node_hash[CONTENT_ROOT])
|
109
|
+
else
|
110
|
+
node_hash[CONTENT_ROOT]
|
111
|
+
end
|
112
|
+
else
|
113
|
+
hash[name] = node_hash[CONTENT_ROOT]
|
114
|
+
end
|
115
|
+
|
116
|
+
hash
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
def self.typecast(schema, xpath, value)
|
123
|
+
type_path = xpath.gsub(/\[\d+\]/,'')
|
124
|
+
type_proc = Qbxml::TYPE_MAP[schema.xpath(type_path).first.try(:text)]
|
125
|
+
raise "#{xpath} is not a valid type" unless type_proc
|
126
|
+
type_proc[value]
|
127
|
+
end
|
128
|
+
|
129
|
+
def self.deep_convert(hash, opts = {}, &block)
|
130
|
+
hash.inject(self.new) do |h, (k,v)|
|
131
|
+
k = k.to_s
|
132
|
+
ignored = IGNORED_KEYS.include?(k)
|
133
|
+
if ignored
|
134
|
+
h[k] = v
|
135
|
+
else
|
136
|
+
key = block_given? ? yield(k) : k
|
137
|
+
h[key] = \
|
138
|
+
case v
|
139
|
+
when Hash
|
140
|
+
deep_convert(v, &block)
|
141
|
+
when Array
|
142
|
+
v.map { |i| i.is_a?(Hash) ? deep_convert(i, &block) : i }
|
143
|
+
else v
|
144
|
+
end
|
145
|
+
end; h
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
data/lib/qbxml/qbxml.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
class Qbxml
|
2
|
+
include Types
|
3
|
+
|
4
|
+
SCHEMA_PATH = File.expand_path('../../../schema', __FILE__)
|
5
|
+
|
6
|
+
SCHEMAS = {
|
7
|
+
qb: "#{SCHEMA_PATH}/qbxmlops70.xml",
|
8
|
+
qbpos: "#{SCHEMA_PATH}/qbposxmlops30.xml"
|
9
|
+
}.freeze
|
10
|
+
|
11
|
+
HIDE_IVARS = [:@doc].freeze
|
12
|
+
|
13
|
+
def initialize(key = :qb)
|
14
|
+
@schema = key
|
15
|
+
@doc = parse_schema(key)
|
16
|
+
end
|
17
|
+
|
18
|
+
# returns all xml nodes matching a specified pattern
|
19
|
+
#
|
20
|
+
def types(pattern = nil)
|
21
|
+
@types ||= @doc.xpath("//*").map { |e| e.name }.uniq
|
22
|
+
|
23
|
+
pattern ?
|
24
|
+
@types.select { |t| t =~ Regexp.new(pattern) } :
|
25
|
+
@types
|
26
|
+
end
|
27
|
+
|
28
|
+
# returns the xml node for the specified type
|
29
|
+
#
|
30
|
+
def describe(type)
|
31
|
+
@doc.xpath("//#{type}").first
|
32
|
+
end
|
33
|
+
|
34
|
+
# converts a hash to qbxml with optional validation
|
35
|
+
#
|
36
|
+
def to_qbxml(hash, opts = {})
|
37
|
+
hash = Qbxml::Hash.from_hash(hash, camelize: true)
|
38
|
+
hash = namespace_qbxml_hash(hash) unless opts[:no_namespace]
|
39
|
+
validate_qbxml_hash(hash) if opts[:validate]
|
40
|
+
|
41
|
+
Qbxml::Hash.to_xml(hash, xml_directive: XML_DIRECTIVES[@schema])
|
42
|
+
end
|
43
|
+
|
44
|
+
# converts qbxml to a hash
|
45
|
+
#
|
46
|
+
def from_qbxml(xml, opts = {})
|
47
|
+
hash = Qbxml::Hash.from_xml(xml, underscore: true, schema: @doc)
|
48
|
+
|
49
|
+
opts[:no_namespace] ? hash : namespace_qbxml_hash(hash)
|
50
|
+
end
|
51
|
+
|
52
|
+
# making this more sane so that it doesn't dump the whole schema doc to stdout
|
53
|
+
# every time
|
54
|
+
#
|
55
|
+
def inspect
|
56
|
+
prefix = "#<#{self.class}:0x#{self.__id__.to_s(16)} "
|
57
|
+
|
58
|
+
(instance_variables - HIDE_IVARS).each do |var|
|
59
|
+
prefix << "#{var}=#{instance_variable_get(var).inspect}"
|
60
|
+
end
|
61
|
+
|
62
|
+
return "#{prefix}>"
|
63
|
+
end
|
64
|
+
|
65
|
+
# private
|
66
|
+
|
67
|
+
def parse_schema(key)
|
68
|
+
File.open(select_schema(key)) { |f| Nokogiri::XML(f) }
|
69
|
+
end
|
70
|
+
|
71
|
+
def select_schema(schema_key)
|
72
|
+
SCHEMAS[schema_key] || raise("invalid schema, must be one of #{SCHEMA.keys.inspect}")
|
73
|
+
end
|
74
|
+
|
75
|
+
# hash to qbxml
|
76
|
+
|
77
|
+
def namespace_qbxml_hash(hash)
|
78
|
+
node = describe(hash.keys.first)
|
79
|
+
return hash unless node
|
80
|
+
|
81
|
+
path = node.path.split('/')[1...-1].reverse
|
82
|
+
path.inject(hash) { |h,p| Qbxml::Hash[ p => h ] }
|
83
|
+
end
|
84
|
+
|
85
|
+
def validate_qbxml_hash(hash, path = [])
|
86
|
+
hash.each do |k,v|
|
87
|
+
next if k == Qbxml::HASH::ATTR_ROOT
|
88
|
+
key_path = path.dup << k
|
89
|
+
if v.is_a?(Hash)
|
90
|
+
validate_qbxml_hash(v, key_path)
|
91
|
+
else
|
92
|
+
validate_xpath(key_path)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def validate_xpath(path)
|
98
|
+
xpath = "/#{path.join('/')}"
|
99
|
+
raise "#{xpath} is not a valid type" if @doc.xpath(xpath).empty?
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
data/lib/qbxml/types.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
module Qbxml::Types
|
2
|
+
|
3
|
+
XML_DIRECTIVES = {
|
4
|
+
:qb => [:qbxml, { version: '6.0' }],
|
5
|
+
:qbpos => [:qbposxml, { version: '3.0' }]
|
6
|
+
}.freeze
|
7
|
+
|
8
|
+
FLOAT_CAST = Proc.new {|d| d ? Float(d) : 0.0}
|
9
|
+
BOOL_CAST = Proc.new {|d| d ? (d == 'True' ? true : false) : false }
|
10
|
+
DATE_CAST = Proc.new {|d| d ? Date.parse(d).strftime("%Y-%m-%d") : Date.today.strftime("%Y-%m-%d") }
|
11
|
+
TIME_CAST = Proc.new {|d| d ? Time.parse(d).xmlschema : Time.now.xmlschema }
|
12
|
+
INT_CAST = Proc.new {|d| d ? Integer(d.to_i) : 0 }
|
13
|
+
STR_CAST = Proc.new {|d| d ? String(d) : ''}
|
14
|
+
|
15
|
+
TYPE_MAP= {
|
16
|
+
"AMTTYPE" => FLOAT_CAST,
|
17
|
+
"BOOLTYPE" => BOOL_CAST,
|
18
|
+
"DATETIMETYPE" => TIME_CAST,
|
19
|
+
"DATETYPE" => DATE_CAST,
|
20
|
+
"ENUMTYPE" => STR_CAST,
|
21
|
+
"FLOATTYPE" => FLOAT_CAST,
|
22
|
+
"GUIDTYPE" => STR_CAST,
|
23
|
+
"IDTYPE" => STR_CAST,
|
24
|
+
"INTTYPE" => INT_CAST,
|
25
|
+
"PERCENTTYPE" => FLOAT_CAST,
|
26
|
+
"PRICETYPE" => FLOAT_CAST,
|
27
|
+
"QUANTYPE" => INT_CAST,
|
28
|
+
"STRTYPE" => STR_CAST,
|
29
|
+
"TIMEINTERVALTYPE" => STR_CAST
|
30
|
+
}
|
31
|
+
|
32
|
+
ACRONYMS = ['AP', 'AR', 'COGS', 'COM', 'UOM', 'QBXML', 'UI', 'AVS', 'ID',
|
33
|
+
'PIN', 'SSN', 'COM', 'CLSID', 'FOB', 'EIN', 'UOM', 'PO', 'PIN', 'QB']
|
34
|
+
|
35
|
+
ActiveSupport::Inflector.inflections do |inflect|
|
36
|
+
ACRONYMS.each { |a| inflect.acronym a }
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
data/lib/qbxml.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require "qbxml/version"
|
2
|
+
|
3
|
+
require 'nokogiri'
|
4
|
+
require 'active_support/builder'
|
5
|
+
require 'active_support/inflections'
|
6
|
+
require 'active_support/core_ext/string'
|
7
|
+
|
8
|
+
class Qbxml; end
|
9
|
+
|
10
|
+
require_relative 'qbxml/types.rb'
|
11
|
+
require_relative 'qbxml/qbxml.rb'
|
12
|
+
require_relative 'qbxml/hash.rb'
|
data/qbxml-dtd6.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'qbxml/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "qbxml-dtd6"
|
8
|
+
gem.version = Qbxml::VERSION
|
9
|
+
gem.authors = ["Alex Skryl"]
|
10
|
+
gem.email = ["rut216@gmail.com"]
|
11
|
+
gem.description = %q{Quickbooks XML Parser}
|
12
|
+
gem.summary = %q{Quickbooks XML Parser and Validation Tool}
|
13
|
+
gem.homepage = ""
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_dependency('activesupport', '>= 3.2.9')
|
21
|
+
gem.add_dependency('nokogiri', '~> 1.5.0')
|
22
|
+
gem.add_dependency('builder', '>= 3.0.0')
|
23
|
+
end
|