Class: Quickbooks::XSD

Inherits:
Object show all
Defined in:
lib/quickbooks/xsd.rb,
lib/quickbooks/xsd/group.rb,
lib/quickbooks/xsd/union.rb,
lib/quickbooks/xsd/choice.rb,
lib/quickbooks/xsd/element.rb,
lib/quickbooks/xsd/sequence.rb,
lib/quickbooks/xsd/attribute.rb,
lib/quickbooks/xsd/restriction.rb,
lib/quickbooks/xsd/simple_type.rb,
lib/quickbooks/xsd/complex_type.rb

Defined Under Namespace

Classes: Attribute, Choice, ComplexType, Element, Group, Restriction, Sequence, SimpleType, Type, Union

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.parse_file(file, path = nil) ⇒ Object



68
69
70
# File 'lib/quickbooks/xsd.rb', line 68

def self.parse_file(file, path=nil)
  Quickbooks::XSD.new.parse_file(file, path)
end

Instance Method Details

#[](key) ⇒ Object

Get any name that is saved in elements.



61
62
63
# File 'lib/quickbooks/xsd.rb', line 61

def [](key)
  elements[key]
end

#elementsObject



43
44
45
# File 'lib/quickbooks/xsd.rb', line 43

def elements
  @elements ||= {}
end

#groupsObject



46
47
48
# File 'lib/quickbooks/xsd.rb', line 46

def groups
  @groups ||= {}
end

#include?(key) ⇒ Boolean

Returns:



64
65
66
# File 'lib/quickbooks/xsd.rb', line 64

def include?(key)
  elements.include?(key)
end

#inspectObject



31
32
33
34
# File 'lib/quickbooks/xsd.rb', line 31

def inspect
  properties_size = properties.size
  sprintf("#<%s:0x%x %s models, %s elements, %s properties, %s types>", self.class.name, __id__, models.size, elements.size - properties_size, properties_size, types.size)
end

#modelsObject



56
57
58
# File 'lib/quickbooks/xsd.rb', line 56

def models
  Hash[*elements.select {|k,e| e.is_model?}.flatten]
end

#parse(source) ⇒ Object



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/quickbooks/xsd.rb', line 87

def parse(source)
  element_stack = []
  waiting_types = Hash.new {|h,k| h[k] = []}
  parser = REXML::Parsers::BaseParser.new(source)
  status = {}
  loop do
    event = parser.pull
    case event[0]
    when :end_document
      break
    when :end_doctype, :start_doctype
      # do nothing
    when :start_element
      # Create a node
      tag_name, attributes = event[1], event[2]
      case tag_name
      when 'xsd:schema'
        # Just the schema element at the top.

      when 'xsd:include'     # Parse this file at this point before continuing.
        parse_file(attributes['schemaLocation'], source_path)

      when 'xsd:element'     # an element. If inside another element any number of levels deep, it's not a declaration, just an inclusion.
        if type_name = attributes.delete('type')
          type = types[type_name]
          attributes.merge!('type' => type)
        end
        element = if element_stack.empty?
          (elements[attributes['name']] ||= Element.new).merge!(attributes)
        elsif attributes.has_key?('ref')
          (elements[attributes['ref']] ||= Element.new).merge(attributes)
        elsif elements.has_key?(attributes['name'])
          elements[attributes['name']].merge(attributes)
        elsif attributes.has_key?('name') && attributes.has_key?('type')
          elements[attributes['name']] ||= Element.new(attributes)
        else
          Element.new(attributes)
        end
        waiting_types[type_name] << element if type_name && type.nil? # When the type is created it will retroactively set the type to this element.
        element_stack.last << element unless element_stack.empty?
        element_stack << element

      when 'xsd:simpleType'  # simpleType means its parent is a property and this defines the possible value
        # If it's being declared on the top level, create a new one
        type = (types[attributes['name']] || SimpleType.new).merge(attributes)
        if element_stack.empty?
          types[type.name] = type
        else
          element_stack.last << type
        end
        element_stack << type
        waiting_types.delete(type.name).each {|e| e.type = type} if type.name && waiting_types.has_key?(type.name)

      when 'xsd:complexType' # defines a complex requirement, usually a sequence
        # If it's being declared on the top level, create a new one
        type = (types[attributes['name']] || ComplexType.new).merge(attributes)
        if element_stack.empty?
          types[type.name] = type
        else
          element_stack.last << type
        end
        element_stack << type
        waiting_types.delete(type.name).each {|e| e.type = type} if type.name && waiting_types.has_key?(type.name)

      when 'xsd:restriction' # restriction defines a restriction on the possible value of an element
			element_stack.last.restriction = Restriction.new(attributes)
        element_stack.last.type = types[attributes['base']] # Sets the base type onto the parent SimpleType
        # TODO: currently we don't step into these restrictions, we're referencing them more explicitly (see xsd:enumeration below)

      when 'xsd:enumeration' # enumerates the values for an ENUM property
        element_stack.last.restriction.enum_values! << attributes.delete('value')
			puts "More attributes for Enumeration: #{attributes.inspect}" unless attributes.empty?

      when 'xsd:group'       # defines a group of elements used together
        group = if element_stack.empty?
          # being declared by name. Save the new attributes into it for sure.
          (groups[attributes['name']] ||= Group.new).merge!(attributes)
        elsif attributes.has_key?('ref')
          # We are referencing a common element, which may or may not already be declared.
          groups[attributes['ref']] ||= Group.new
        else
          # We're not declaring nor are we referencing a declared element.
          Group.new
        end.merge(attributes)
        element_stack.last << group unless element_stack.empty?
        element_stack << group

      when 'xsd:sequence'    # defines a sequence of elements/element-groups
        sequence = Sequence.new(attributes)
        element_stack.last << sequence
        element_stack << sequence

      when 'xsd:choice'      # choice means include any one of the enclosed elements/element-groups
        choice = Choice.new(attributes)
        element_stack.last << choice
        element_stack << choice
			puts "More attributes for Choice: #{attributes.inspect}" unless attributes.empty?

      when 'xsd:union'
        union = Union.new(attributes)
        element_stack.last << union
        element_stack << union
			puts "More attributes for Union: #{attributes.inspect}" unless attributes.empty?

      when 'xsd:attribute'   # an attribute of the parent element
        if type = attributes.delete('type')
          type = types[type] # assuming that all types are declared before they're referenced.
          attributes.merge!('type' => type)
        end
        attribute = Attribute.new(attributes)
        element_stack.last << attribute
        element_stack << attribute

      when 'xsd:maxLength'   # a restriction of a STRTYPE property
			element_stack.last.restriction.maxLength = attributes.delete('value')
			puts "More attributes for maxLength: #{attributes.inspect}" unless attributes.empty?

      when 'xsd:pattern'     # a restriction of a STRTYPE property
			element_stack.last.restriction.pattern = attributes.delete('value')
			puts "More attributes for pattern: #{attributes.inspect}" unless attributes.empty?

      when 'xsd:maxInclusive'
      when 'xsd:minInclusive'
      when 'xsd:simpleContent'
      when 'xsd:extension'
      else
        warn "!! Other element: #{tag_name} (#{attributes.inspect})"
      end
    when :end_element
      name = event[1]
      case name
      when 'xsd:element'
        element_stack.pop
      when 'xsd:simpleType'
        element_stack.pop
      when 'xsd:group'
        element_stack.pop
      when 'xsd:union'
        element_stack.pop
      when 'xsd:choice'
        element_stack.pop
      when 'xsd:sequence'
        element_stack.pop
      when 'xsd:complexType'
        element_stack.pop
      when 'xsd:attribute'
        element_stack.pop
      end
    when :text, :cdata
      puts "!! Text Data: #{event[1]}" if event[1].strip.length > 0
    end
  end

self
end

#parse_file(file, path = nil) ⇒ Object



72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/quickbooks/xsd.rb', line 72

def parse_file(file, path=nil)
  file = (path || source_path) + '/' + file unless path.nil? && source_path.nil?

  if File.exists?(file)
    file = File.expand_path(file)
    source_paths << File.dirname(file)
    # puts "Parsing #{file}..."
    xsd = parse(File.read(file))
    source_paths.pop
    xsd
  else
    raise "XSD source file #{file} does not exist!"
  end
end

#propertiesObject



53
54
55
# File 'lib/quickbooks/xsd.rb', line 53

def properties
  Hash[*elements.select {|k,e| e.is_property?}.flatten]
end

#source_pathObject



39
40
41
# File 'lib/quickbooks/xsd.rb', line 39

def source_path
  source_paths.last
end

#source_pathsObject



36
37
38
# File 'lib/quickbooks/xsd.rb', line 36

def source_paths
  @source_paths ||= []
end

#typesObject



49
50
51
# File 'lib/quickbooks/xsd.rb', line 49

def types
  @types ||= {}
end