quickbooks_api 0.0.7 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,5 @@
1
+ - tests
2
+ - support for node type constraints and validation
3
+ - hash to obj doesn't work when converting from an inner level hash ex. 'customer_mod_rq'
4
+ - add processing instruction to node set <?qbposxml version="3.0"?>
5
+ - handle node attributes properly
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/api'
11
- require 'quickbooks/support/logger'
12
- require 'quickbooks/support/qbxml'
13
- require 'quickbooks/support/class_builder'
14
- require 'quickbooks/qbxml_base'
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'
@@ -1,143 +1,103 @@
1
1
  class Quickbooks::API
2
- include Quickbooks::Support
3
- include Quickbooks::Support::API
4
- include Quickbooks::Support::QBXML
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
- @@instances = {}
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
- @schema_type = schema_type
11
- use_disk_cache, log_level = opts.values_at(:use_disk_cache, :log_level)
10
+ def initialize(schema_type = nil, opts = {})
11
+ self.class.check_schema_type!(schema_type)
12
+ @schema_type = schema_type
12
13
 
13
- unless valid_schema_type?
14
- raise(ArgumentError, "schema type required: #{valid_schema_types.inspect}")
15
- end
14
+ @dtd_parser = Quickbooks::DtdParser.new(schema_type)
15
+ @qbxml_parser = Quickbooks::QbxmlParser.new(schema_type)
16
16
 
17
- @dtd_file = get_dtd_file
18
- @dtd_parser = DtdParser.new(schema_type)
19
- @qbxml_parser = QbxmlParser.new(schema_type)
17
+ load_qb_classes
18
+ @@instances[schema_type] = self
19
+ end
20
20
 
21
- load_qb_classes(use_disk_cache)
22
- @@instances[schema_type] = self
23
- end
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
- # returns the last created api instance
26
- def self.[](schema_type)
27
- @@instances[schema_type] || self.new(schema_type)
28
- end
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
- def container
31
- get_container_class
32
- end
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
- cached_classes
36
- end
39
+ def qbxml_classes
40
+ cached_classes
41
+ end
37
42
 
38
- def find(class_name)
39
- class_name = class_name.to_s
40
- cached_classes.find { |c| to_attribute_name(c) == class_name }
41
- end
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
- case pattern
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
- qb_obj = qbxml_to_obj(qbxml)
65
- unless include_container
66
- qb_obj.inner_attributes
67
- else
68
- qb_obj.attributes
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
- key = data.keys.first
77
- value = data[key]
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
- key_path = find_nested_key(container.template(true), key.to_s)
80
- raise(RuntimeError, "#{key} class not found in api template") unless key_path
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
- def load_qb_classes(use_disk_cache = false)
104
- if use_disk_cache
105
- disk_cache = Dir["#{get_disk_cache_path}/*.rb"]
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
- # load the container class template into memory (significantly speeds up wrapping of partial data hashes)
117
- get_container_class.template(true, use_disk_cache, use_disk_cache)
118
- true
119
- end
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::Support::ClassBuilder
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
- attr_name, qb_type = parse_leaf_node_data(xml_obj)
14
- if parent_class
15
- add_casting_attribute(parent_class, attr_name, qb_type)
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
- klass = build_qbxml_class(xml_obj)
21
- attr_name = to_attribute_name(xml_obj)
22
- if parent_class
23
- add_strict_attribute(parent_class, attr_name, klass)
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
- def process_comment_node(xml_obj, parent_class)
29
- parent_class
30
- end
31
-
32
- # helpers
22
+ #TODO: stub
23
+ def process_comment_node(xml_obj, parent_class)
24
+ parent_class
25
+ end
33
26
 
34
- def build_qbxml_class(xml_obj)
35
- obj_name = xml_obj.name
36
- unless qbxml_class_defined?(obj_name)
37
- klass = Class.new(QbxmlBase)
38
- get_schema_namespace.const_set(obj_name, klass)
39
- xml_obj = set_required_attributes(xml_obj)
40
- add_xml_template(klass, xml_obj.to_xml)
41
- else
42
- klass = get_schema_namespace.const_get(obj_name)
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