quickbooks_api 0.0.7 → 0.1.0
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.
- data/README +74 -0
- data/TODO +5 -0
- data/lib/quickbooks.rb +12 -10
- data/lib/quickbooks/api.rb +72 -112
- data/lib/quickbooks/config.rb +59 -0
- data/lib/quickbooks/dtd_parser.rb +30 -35
- data/lib/quickbooks/logger.rb +20 -0
- data/lib/quickbooks/{support → parser}/class_builder.rb +7 -7
- data/lib/quickbooks/parser/qbxml_base.rb +128 -0
- data/lib/quickbooks/parser/xml_generation.rb +52 -0
- data/lib/quickbooks/{support/qbxml.rb → parser/xml_parsing.rb} +16 -20
- data/lib/quickbooks/qbxml_parser.rb +68 -68
- data/lib/quickbooks/support/inflection.rb +18 -0
- data/lib/quickbooks/support/monkey_patches.rb +48 -0
- data/spec/quickbooks/api_spec.rb +13 -10
- data/spec/quickbooks/class_builder_spec.rb +1 -1
- data/spec/quickbooks/config_spec.rb +72 -0
- data/spec/quickbooks/inflection_spec.rb +18 -0
- data/spec/quickbooks/logger_spec.rb +1 -13
- data/spec/quickbooks/monkey_patches_spec.rb +11 -0
- data/spec/quickbooks/qbxml_base_spec.rb +23 -0
- data/spec/quickbooks/xml_generation_spec.rb +5 -0
- data/spec/quickbooks/xml_parsing_spec.rb +36 -0
- metadata +20 -30
- data/lib/quickbooks/qbxml_base.rb +0 -212
- data/lib/quickbooks/support.rb +0 -33
- data/lib/quickbooks/support/api.rb +0 -98
- data/lib/quickbooks/support/logger.rb +0 -56
- data/ruby_schema/qb/templates/.placeholder +0 -0
- data/ruby_schema/qbpos/templates/.placeholder +0 -0
- data/spec/quickbooks/qbxml_spec.rb +0 -23
- data/spec/quickbooks/support_spec.rb +0 -118
data/README
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
INITIALIZATION
|
2
|
+
------------------------------------------------------------------------------
|
3
|
+
|
4
|
+
# supported data types are:
|
5
|
+
# :qb - Quickbooks
|
6
|
+
# :qbpos - Quickbooks Point Of Sale
|
7
|
+
|
8
|
+
api = Quickbooks::API.instance(:qbpos)
|
9
|
+
|
10
|
+
# shorthand initialization
|
11
|
+
api = Quickbooks::API[:qbpos]
|
12
|
+
|
13
|
+
|
14
|
+
API INTROSPECTION
|
15
|
+
------------------------------------------------------------------------------
|
16
|
+
|
17
|
+
# returns all available wrapper classes
|
18
|
+
api.qbxml_classes
|
19
|
+
|
20
|
+
# returns the top level wrapper class for the api
|
21
|
+
api.container
|
22
|
+
|
23
|
+
# find specific qbxml class by name
|
24
|
+
api.find('customer_mod_rq')
|
25
|
+
|
26
|
+
# find all qbxml classes that contain a pattern
|
27
|
+
api.grep(/_mod_rq/)
|
28
|
+
|
29
|
+
# returns a hash of all the data types for a wrapper class
|
30
|
+
wrapper_class.template
|
31
|
+
|
32
|
+
# returns the full teplate for a wrapper class (SLOOOW for top level classes)
|
33
|
+
wrapper_class.template(true)
|
34
|
+
|
35
|
+
# returns all the supported fields for the wrapper class
|
36
|
+
wrapper_class.attribute_names
|
37
|
+
|
38
|
+
# returns the qbxml template used to generate the wrapper class
|
39
|
+
wrapper_class.xml_template
|
40
|
+
|
41
|
+
|
42
|
+
QBXML TO RUBY
|
43
|
+
------------------------------------------------------------------------------
|
44
|
+
|
45
|
+
# wrap qbxml data in a qbxml object
|
46
|
+
o = api.qbxml_to_obj(qbxml)
|
47
|
+
|
48
|
+
# convert qbxml object to hash
|
49
|
+
o.inner_attributes
|
50
|
+
|
51
|
+
# same as above but includes the top level containers
|
52
|
+
o.attributes
|
53
|
+
|
54
|
+
# retrieves attributes from nested objects
|
55
|
+
o.attributes(true)
|
56
|
+
|
57
|
+
# directly convert raw qbxml to a hash
|
58
|
+
h = api.qbxml_to_hash(qbxml)
|
59
|
+
|
60
|
+
# same as above but includes the top level containers
|
61
|
+
h = api.qbxml_to_hash(qbxml, true)
|
62
|
+
|
63
|
+
|
64
|
+
RUBY TO QBXML
|
65
|
+
------------------------------------------------------------------------------
|
66
|
+
|
67
|
+
# convert a hash to a qbxml object (automagically creates the top level containers)
|
68
|
+
o = api.hash_to_obj(data_hash)
|
69
|
+
|
70
|
+
# convert a qbxml object to raw qbxml
|
71
|
+
o.to_qbxml.to_s
|
72
|
+
|
73
|
+
# convert a hash directly to raw qbxml
|
74
|
+
qbxml = api.hash_to_qbxml(data_hash)
|
data/TODO
ADDED
data/lib/quickbooks.rb
CHANGED
@@ -1,17 +1,19 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
require 'ruby2ruby' # must be version 1.2.1 of ruby2ruby
|
3
|
-
require 'nokogiri'
|
4
|
-
require 'active_support'
|
5
|
-
require 'active_support/core_ext'
|
6
2
|
|
7
3
|
module Quickbooks; end
|
4
|
+
module Quickbooks::QBXML; end
|
5
|
+
module Quickbooks::QBPOSXML; end
|
6
|
+
module Quickbooks::Support; end
|
7
|
+
module Quickbooks::Parser; end
|
8
8
|
|
9
|
-
require 'quickbooks/support'
|
10
|
-
require 'quickbooks/support/
|
11
|
-
require 'quickbooks/
|
12
|
-
require 'quickbooks/
|
13
|
-
require 'quickbooks/
|
14
|
-
require 'quickbooks/
|
9
|
+
require 'quickbooks/support/monkey_patches'
|
10
|
+
require 'quickbooks/support/inflection'
|
11
|
+
require 'quickbooks/logger'
|
12
|
+
require 'quickbooks/config'
|
13
|
+
require 'quickbooks/parser/xml_parsing'
|
14
|
+
require 'quickbooks/parser/xml_generation'
|
15
|
+
require 'quickbooks/parser/class_builder'
|
16
|
+
require 'quickbooks/parser/qbxml_base'
|
15
17
|
require 'quickbooks/qbxml_parser'
|
16
18
|
require 'quickbooks/dtd_parser'
|
17
19
|
require 'quickbooks/api'
|
data/lib/quickbooks/api.rb
CHANGED
@@ -1,143 +1,103 @@
|
|
1
1
|
class Quickbooks::API
|
2
|
-
include Quickbooks::
|
3
|
-
include Quickbooks::
|
4
|
-
include Quickbooks::Support::
|
2
|
+
include Quickbooks::Logger
|
3
|
+
include Quickbooks::Config
|
4
|
+
include Quickbooks::Support::Inflection
|
5
5
|
|
6
|
-
attr_reader :dtd_parser, :qbxml_parser, :schema_type
|
7
|
-
|
6
|
+
attr_reader :dtd_parser, :qbxml_parser, :schema_type
|
7
|
+
private_class_method :new
|
8
|
+
@@instances = {}
|
8
9
|
|
9
|
-
def initialize(schema_type = nil, opts = {})
|
10
|
-
|
11
|
-
|
10
|
+
def initialize(schema_type = nil, opts = {})
|
11
|
+
self.class.check_schema_type!(schema_type)
|
12
|
+
@schema_type = schema_type
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
end
|
14
|
+
@dtd_parser = Quickbooks::DtdParser.new(schema_type)
|
15
|
+
@qbxml_parser = Quickbooks::QbxmlParser.new(schema_type)
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
load_qb_classes
|
18
|
+
@@instances[schema_type] = self
|
19
|
+
end
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
# simple singleton constructor without caching support
|
22
|
+
#
|
23
|
+
def self.[](schema_type)
|
24
|
+
@@instances[schema_type] || new(schema_type)
|
25
|
+
end
|
24
26
|
|
25
|
-
#
|
26
|
-
|
27
|
-
|
28
|
-
|
27
|
+
# full singleton constructor
|
28
|
+
#
|
29
|
+
def self.instance(schema_type = nil, opts = {})
|
30
|
+
@@instances[schema_type] || new(schema_type, opts)
|
31
|
+
end
|
29
32
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
+
# user friendly api decorators. Not used anywhere else.
|
34
|
+
#
|
35
|
+
def container
|
36
|
+
container_class
|
37
|
+
end
|
33
38
|
|
34
|
-
def qbxml_classes
|
35
|
-
|
36
|
-
end
|
39
|
+
def qbxml_classes
|
40
|
+
cached_classes
|
41
|
+
end
|
37
42
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
43
|
+
# api introspection
|
44
|
+
#
|
45
|
+
def find(class_name)
|
46
|
+
cached_classes.find { |c| underscore(c) == class_name.to_s }
|
47
|
+
end
|
42
48
|
|
43
|
-
def grep(pattern)
|
44
|
-
|
45
|
-
when Regexp
|
46
|
-
cached_classes.select { |c| to_attribute_name(c).match(pattern) }
|
47
|
-
when String
|
48
|
-
cached_classes.select { |c| to_attribute_name(c).include?(pattern) }
|
49
|
+
def grep(pattern)
|
50
|
+
cached_classes.select { |c| underscore(c).match(/#{pattern}/) }
|
49
51
|
end
|
50
|
-
end
|
51
52
|
|
52
|
-
# QBXML 2 RUBY
|
53
|
+
# QBXML 2 RUBY
|
53
54
|
|
54
|
-
def qbxml_to_obj(qbxml)
|
55
|
-
case qbxml
|
56
|
-
when IO
|
57
|
-
qbxml_parser.parse_file(qbxml)
|
58
|
-
else
|
55
|
+
def qbxml_to_obj(qbxml)
|
59
56
|
qbxml_parser.parse(qbxml)
|
60
57
|
end
|
61
|
-
end
|
62
58
|
|
63
|
-
def qbxml_to_hash(qbxml, include_container = false)
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
59
|
+
def qbxml_to_hash(qbxml, include_container = false)
|
60
|
+
if include_container
|
61
|
+
qbxml_to_obj(qbxml).attributes
|
62
|
+
else
|
63
|
+
qbxml_to_obj(qbxml).inner_attributes
|
64
|
+
end
|
69
65
|
end
|
70
|
-
end
|
71
|
-
|
72
66
|
|
73
|
-
# RUBY 2 QBXML
|
67
|
+
# RUBY 2 QBXML
|
74
68
|
|
75
|
-
def hash_to_obj(data)
|
76
|
-
|
77
|
-
|
69
|
+
def hash_to_obj(data)
|
70
|
+
key, value = data.detect { |name, value| name != :xml_attributes }
|
71
|
+
key_path = container_class.template(true).path_to_nested_key(key.to_s)
|
72
|
+
raise(RuntimeError, "#{key} class not found in api template") unless key_path
|
78
73
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
wrapped_data = build_hash_wrapper(key_path, value)
|
83
|
-
container.new(wrapped_data)
|
84
|
-
end
|
85
|
-
|
86
|
-
def hash_to_qbxml(data)
|
87
|
-
hash_to_obj(data).to_qbxml
|
88
|
-
end
|
89
|
-
|
90
|
-
# Disk Cache
|
91
|
-
|
92
|
-
def clear_disk_cache(rebuild = false)
|
93
|
-
qbxml_cache = Dir["#{get_disk_cache_path}/*.rb"]
|
94
|
-
template_cache = Dir["#{get_template_cache_path}/*.yml"]
|
95
|
-
File.delete(*(qbxml_cache + template_cache))
|
96
|
-
load_qb_classes(rebuild)
|
97
|
-
end
|
74
|
+
wrapped_data = Hash.nest(key_path, value)
|
75
|
+
container_class.new(wrapped_data)
|
76
|
+
end
|
98
77
|
|
78
|
+
def hash_to_qbxml(data)
|
79
|
+
hash_to_obj(data).to_qbxml
|
80
|
+
end
|
99
81
|
|
100
82
|
private
|
101
83
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
if disk_cache.empty?
|
107
|
-
log.info "Warning: on disk schema cache is empty, rebuilding..."
|
108
|
-
rebuild_schema_cache(false, true)
|
109
|
-
else
|
110
|
-
disk_cache.each {|file| require file }
|
111
|
-
end
|
112
|
-
else
|
113
|
-
rebuild_schema_cache(false, false)
|
84
|
+
def load_qb_classes
|
85
|
+
rebuild_schema_cache(false)
|
86
|
+
load_full_container_template
|
87
|
+
container_class
|
114
88
|
end
|
115
89
|
|
116
|
-
#
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
# rebuilds schema cache in memory and writes to disk if desired
|
122
|
-
#
|
123
|
-
def rebuild_schema_cache(force = false, write_to_disk = false)
|
124
|
-
dtd_parser.parse_file(@dtd_file) if (cached_classes.empty? || force)
|
125
|
-
dump_cached_classes if write_to_disk
|
126
|
-
end
|
127
|
-
|
128
|
-
# writes dynamically generated api classes to disk
|
129
|
-
#
|
130
|
-
def dump_cached_classes
|
131
|
-
cached_classes.each do |c|
|
132
|
-
File.open("#{get_disk_cache_path}/#{to_attribute_name(c)}.rb", 'w') do |f|
|
133
|
-
f << Ruby2Ruby.translate(c)
|
134
|
-
end
|
90
|
+
# rebuilds schema cache in memory
|
91
|
+
#
|
92
|
+
def rebuild_schema_cache(force = false)
|
93
|
+
dtd_parser.parse_file(dtd_file) if (cached_classes.empty? || force)
|
135
94
|
end
|
136
|
-
end
|
137
|
-
|
138
|
-
def self.log
|
139
|
-
@@log ||= Logger.new(STDOUT, DEFAULT_LOG_LEVEL)
|
140
|
-
end
|
141
95
|
|
96
|
+
# load the recursive container class template into memory (significantly
|
97
|
+
# speeds up wrapping of partial data hashes)
|
98
|
+
#
|
99
|
+
def load_full_container_template(use_disk_cache = false)
|
100
|
+
container_class.template(true)
|
101
|
+
end
|
142
102
|
|
143
103
|
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Quickbooks::Config
|
2
|
+
|
3
|
+
API_ROOT = File.join(File.dirname(__FILE__), '..', '..').freeze
|
4
|
+
XML_SCHEMA_PATH = File.join(API_ROOT, 'xml_schema').freeze
|
5
|
+
RUBY_SCHEMA_PATH = File.join(API_ROOT, 'ruby_schema').freeze
|
6
|
+
|
7
|
+
SCHEMA_MAP = {
|
8
|
+
:qb => {:dtd_file => "qbxmlops70.xml",
|
9
|
+
:namespace => Quickbooks::QBXML,
|
10
|
+
:container_class => 'QBXML'
|
11
|
+
}.freeze,
|
12
|
+
:qbpos => {:dtd_file => "qbposxmlops30.xml",
|
13
|
+
:namespace => Quickbooks::QBPOSXML,
|
14
|
+
:container_class => 'QBPOSXML'
|
15
|
+
}.freeze,
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
def self.included(klass)
|
19
|
+
klass.extend ClassMethods
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def container_class
|
25
|
+
schema_namespace.const_get(SCHEMA_MAP[schema_type][:container_class])
|
26
|
+
end
|
27
|
+
|
28
|
+
def dtd_file
|
29
|
+
"#{XML_SCHEMA_PATH}/#{SCHEMA_MAP[schema_type][:dtd_file]}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def schema_namespace
|
33
|
+
SCHEMA_MAP[schema_type][:namespace]
|
34
|
+
end
|
35
|
+
|
36
|
+
# introspection
|
37
|
+
|
38
|
+
def cached_classes
|
39
|
+
schema_namespace.constants.map { |const| schema_namespace.const_get(const) }
|
40
|
+
end
|
41
|
+
|
42
|
+
module ClassMethods
|
43
|
+
|
44
|
+
def check_schema_type!(schema_type)
|
45
|
+
unless SCHEMA_MAP.include?(schema_type)
|
46
|
+
raise(ArgumentError, "valid schema type required: #{valid_schema_types.inspect}")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def valid_schema_types
|
53
|
+
SCHEMA_MAP.keys
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
end
|
@@ -1,47 +1,42 @@
|
|
1
1
|
class Quickbooks::DtdParser < Quickbooks::QbxmlParser
|
2
|
-
include Quickbooks::
|
3
|
-
|
4
|
-
def parse_file(qbxml_file)
|
5
|
-
parse(
|
6
|
-
cleanup_qbxml(
|
7
|
-
File.read(qbxml_file)))
|
8
|
-
end
|
2
|
+
include Quickbooks::Parser::ClassBuilder
|
9
3
|
|
10
4
|
private
|
11
5
|
|
12
|
-
def process_leaf_node(xml_obj, parent_class)
|
13
|
-
|
14
|
-
|
15
|
-
|
6
|
+
def process_leaf_node(xml_obj, parent_class)
|
7
|
+
attr_name, qb_type = parse_leaf_node_data(xml_obj)
|
8
|
+
if parent_class
|
9
|
+
add_casting_attribute(parent_class, attr_name, qb_type)
|
10
|
+
end
|
16
11
|
end
|
17
|
-
end
|
18
12
|
|
19
|
-
def process_non_leaf_node(xml_obj, parent_class)
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
13
|
+
def process_non_leaf_node(xml_obj, parent_class)
|
14
|
+
klass = build_qbxml_class(xml_obj)
|
15
|
+
attr_name = underscore(xml_obj)
|
16
|
+
if parent_class
|
17
|
+
add_strict_attribute(parent_class, attr_name, klass)
|
18
|
+
end
|
19
|
+
klass
|
24
20
|
end
|
25
|
-
klass
|
26
|
-
end
|
27
21
|
|
28
|
-
|
29
|
-
parent_class
|
30
|
-
|
31
|
-
|
32
|
-
# helpers
|
22
|
+
#TODO: stub
|
23
|
+
def process_comment_node(xml_obj, parent_class)
|
24
|
+
parent_class
|
25
|
+
end
|
33
26
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
27
|
+
# helpers
|
28
|
+
|
29
|
+
def build_qbxml_class(xml_obj)
|
30
|
+
obj_name = xml_obj.name
|
31
|
+
unless schema_namespace.const_defined?(obj_name)
|
32
|
+
klass = Class.new(Quickbooks::Parser::QbxmlBase)
|
33
|
+
schema_namespace.const_set(obj_name, klass)
|
34
|
+
klass.xml_attributes = parse_xml_attributes(xml_obj)
|
35
|
+
add_xml_template(klass, xml_obj.to_xml)
|
36
|
+
else
|
37
|
+
klass = schema_namespace.const_get(obj_name)
|
38
|
+
end
|
39
|
+
klass
|
43
40
|
end
|
44
|
-
klass
|
45
|
-
end
|
46
41
|
|
47
42
|
end
|