xsd-reader 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2b0bde5f374f9fa52cf79ab8ad070a105006ded8
4
- data.tar.gz: 3b20b79a58c606b597fbaf540b1c278030bc99c6
3
+ metadata.gz: bdb069eb93be08cacdb61072c710f0571fc294ea
4
+ data.tar.gz: 96031d641025b2f75e88ed7dcc8dd3f4c0a017d4
5
5
  SHA512:
6
- metadata.gz: 2ab56370054c6373fa72c3a2f8cfc1832804e7f325d806a0e1d7c246ffc9870abc36b2aa0fb7e055421fb6c4c3b21af32adecc95553c516867eb4f3cfec24328
7
- data.tar.gz: f515fa9b00bd154df2f26c001dce0b8149437b21917a23cd7480966a0c1359a1a1cdab1830b54b7b9f960879ca55c6d18118836f21d82663d42313b2c225707b
6
+ metadata.gz: 3dfdb49aa63bac9c31d62a773ff00bf9c5cdf4bf355dc0e41e68edba097776c369d372175c4f59c9bb8e34cc015ca0b29cf4871d925f7368e8d596838953cc09
7
+ data.tar.gz: 1ec845bc1fa7a027f61ee03fff370b8947ad8598f4e53c9093c383a773e5580aab52811479964e804c029703e88fbce9f70c7df49b120bbd9b92d239086f1e96
data/Gemfile.lock CHANGED
@@ -3,6 +3,7 @@ PATH
3
3
  specs:
4
4
  xsd-reader (0.0.1)
5
5
  nokogiri
6
+ rest-client
6
7
 
7
8
  GEM
8
9
  remote: https://rubygems.org/
@@ -11,9 +12,19 @@ GEM
11
12
  columnize (= 0.9.0)
12
13
  columnize (0.9.0)
13
14
  diff-lcs (1.2.5)
15
+ domain_name (0.5.24)
16
+ unf (>= 0.0.5, < 1.0.0)
17
+ http-cookie (1.0.2)
18
+ domain_name (~> 0.5)
19
+ mime-types (2.6.1)
14
20
  mini_portile (0.6.2)
21
+ netrc (0.10.3)
15
22
  nokogiri (1.6.6.2)
16
23
  mini_portile (~> 0.6.0)
24
+ rest-client (1.8.0)
25
+ http-cookie (>= 1.0.2, < 2.0)
26
+ mime-types (>= 1.16, < 3.0)
27
+ netrc (~> 0.7)
17
28
  rspec (3.3.0)
18
29
  rspec-core (~> 3.3.0)
19
30
  rspec-expectations (~> 3.3.0)
@@ -27,6 +38,9 @@ GEM
27
38
  diff-lcs (>= 1.2.0, < 2.0)
28
39
  rspec-support (~> 3.3.0)
29
40
  rspec-support (3.3.0)
41
+ unf (0.1.4)
42
+ unf_ext
43
+ unf_ext (0.0.7.1)
30
44
 
31
45
  PLATFORMS
32
46
  ruby
data/README.md CHANGED
@@ -7,20 +7,21 @@ XsdReader provides easy and flexible access to XSD information
7
7
 
8
8
  Rubygems:
9
9
 
10
- `
10
+ ```
11
11
  gem install xsd-reader
12
- `
12
+ ```
13
13
 
14
14
  Bundler:
15
15
 
16
- `
16
+ ```ruby
17
17
  gem 'xsd-reader'
18
- `
18
+ ````
19
19
 
20
20
  ## Examples
21
21
 
22
22
  Load xsd
23
23
  ```ruby
24
+ require 'xsd_reader'
24
25
  reader = XsdReader::XML.new(:xsd_file => 'ddex-ern-v36.xsd')
25
26
  ```
26
27
 
@@ -1,8 +1,5 @@
1
1
  module XsdReader
2
-
3
2
  class Choice
4
3
  include Shared
5
-
6
4
  end # class Choice
7
-
8
5
  end # module XsdReader
@@ -0,0 +1,5 @@
1
+ module XsdReader
2
+ class ComplexContent
3
+ include Shared
4
+ end # class ComplexContent
5
+ end
@@ -10,11 +10,11 @@ module XsdReader
10
10
  end
11
11
 
12
12
  def attributes
13
- super + (complex_type ? complex_type.attributes : [])
13
+ @_element_attributes ||= super + (complex_type ? complex_type.attributes : [])
14
14
  end
15
15
 
16
16
  def complex_type
17
- super || linked_complex_type
17
+ @_element_complex_type ||= super || linked_complex_type
18
18
  end
19
19
 
20
20
  def min_occurs
@@ -39,10 +39,6 @@ module XsdReader
39
39
  !required?
40
40
  end
41
41
 
42
- def parent
43
- node.parent
44
- end
45
-
46
42
  def family_tree(stack = [])
47
43
  logger.warn('Usage of the family tree function is not recommended as it can take very long to execute and is very memory intensive')
48
44
  return @_cached_family_tree if @_cached_family_tree
@@ -1,5 +1,13 @@
1
1
  module XsdReader
2
2
  class Extension
3
3
  include Shared
4
+
5
+ def linked_complex_type
6
+ @linked_complex_type ||= (schema_for_namespace(base_namespace) || schema).complex_types.find{|ct| ct.name == (base_name)}
7
+ end
8
+
9
+ def ordered_elements
10
+ (linked_complex_type ? linked_complex_type.ordered_elements : []) + super
11
+ end
4
12
  end # class Schema
5
13
  end
@@ -0,0 +1,56 @@
1
+ require 'rest-client'
2
+
3
+ module XsdReader
4
+ class Import
5
+ include Shared
6
+
7
+ def namespace
8
+ node.attributes['namespace'] ? node.attributes['namespace'].value : nil
9
+ end
10
+
11
+ def schema_location
12
+ node.attributes['schemaLocation'] ? node.attributes['schemaLocation'].value : nil
13
+ end
14
+
15
+ def reader
16
+ return @reader || options[:reader] if @reader || options[:reader]
17
+ if download_path
18
+ File.write(download_path, download) if !File.file?(download_path)
19
+ return @reader = XsdReader::XML.new(:xsd_file => download_path)
20
+ end
21
+
22
+ return @reader = XsdReader::XML.new(:xsd_xml => download)
23
+ end
24
+
25
+ def uri
26
+ if namespace =~ /\.xsd$/
27
+ namespace
28
+ else
29
+ namespace.gsub(/#{File.basename(schema_location, '.*')}$/, '').to_s + schema_location
30
+ end
31
+ end
32
+
33
+ def download
34
+ @download ||= download_uri(self.uri)
35
+ end
36
+
37
+ def download_path
38
+ # we need the parent XSD's path
39
+ return nil if options[:xsd_file].nil?
40
+ parent_path = File.dirname(options[:xsd_file])
41
+ File.join(parent_path, File.basename(schema_location))
42
+ end
43
+
44
+ def local_xml
45
+ File.file?(download_path) ? File.read(download_path) : download
46
+ end
47
+
48
+ private
49
+
50
+ def download_uri(uri)
51
+ logger.info "Downloading import schema from (uri)"
52
+ response = RestClient.get uri
53
+ return response.body
54
+ end
55
+ end # class Import
56
+ end
@@ -1,5 +1,40 @@
1
1
  module XsdReader
2
2
  class Schema
3
3
  include Shared
4
+
5
+ def schema
6
+ return self
7
+ end
8
+
9
+ def target_namespace
10
+ node && node.attributes['targetNamespace'] ? node.attributes['targetNamespace'].value : nil
11
+ end
12
+
13
+ def namespaces
14
+ self.node ? self.node.namespaces : {}
15
+ end
16
+
17
+ def targets_namespace?(ns)
18
+ target_namespace == ns || target_namespace == namespaces["xmlns:#{ns}"]
19
+ end
20
+
21
+ def imports
22
+ @imports ||= map_children("xs:import")
23
+ end
24
+
25
+ def mappable_children(xml_name)
26
+ result = super
27
+ result += import_mappable_children(xml_name) if xml_name != 'xs:import'
28
+ return result.to_a
29
+ end
30
+
31
+ def import_mappable_children(xml_name)
32
+ self.imports.map{|import| import.reader.schema.mappable_children(xml_name)}.flatten
33
+ end
34
+
35
+ def import_by_namespace(ns)
36
+ aliases = [ns, namespaces["xmlns:#{(ns || '').gsub(/^xmlns\:/, '')}"]].compact
37
+ return imports.find{|import| aliases.include?(import.namespace)}
38
+ end
4
39
  end # class Schema
5
40
  end
@@ -12,7 +12,10 @@ module XsdReader
12
12
  end
13
13
 
14
14
  def logger
15
+ return @logger if @logger
15
16
  @logger ||= options[:logger] || Logger.new(STDOUT)
17
+ @logger.level = Logger::WARN
18
+ return @logger
16
19
  end
17
20
 
18
21
  def node
@@ -23,22 +26,23 @@ module XsdReader
23
26
  node.search("./*")
24
27
  end
25
28
 
26
- def [](name)
27
- # got an array of name? recursive search through generations
28
- if name.is_a?(Array)
29
- el = self
30
- name.each{|child_name| el = el.nil? ? nil : el.elements.find{|child| child.name == child_name}}
31
- return el
32
- end
29
+ def [](*args)
30
+ # now name is always an array
31
+ names = args.flatten
33
32
 
34
- # starts with an @-symbol? Then we're looking for an attribute
33
+ result = self
35
34
 
36
- if name =~ /^\@/
37
- attr_name = name.gsub(/^\@/, '')
38
- return attributes.find{|attr| attr.name == attr_name}
35
+ names.each do |curname|
36
+ next if result.nil?
37
+ if curname.to_s =~ /^\@/
38
+ attr_name = curname.to_s.gsub(/^\@/, '')
39
+ result = result.attributes.find{|attr| attr.name == attr_name}
40
+ else
41
+ result = result.elements.find{|child| child.name == curname.to_s}
42
+ end
39
43
  end
40
44
 
41
- elements.find{|el| el.name == name}
45
+ return result
42
46
  end
43
47
 
44
48
  #
@@ -60,6 +64,19 @@ module XsdReader
60
64
  type ? type.split(':').first : nil
61
65
  end
62
66
 
67
+ # base stuff belongs to extension type objects only, but let's be flexible
68
+ def base
69
+ node.attributes['base'] ? node.attributes['base'].value : nil
70
+ end
71
+
72
+ def base_name
73
+ base ? base.split(':').last : nil
74
+ end
75
+
76
+ def base_namespace
77
+ base ? base.split(':').first : nil
78
+ end
79
+
63
80
  #
64
81
  # Node to class mapping
65
82
  #
@@ -72,7 +89,10 @@ module XsdReader
72
89
  'xs:complexType' => ComplexType,
73
90
  'xs:sequence' => Sequence,
74
91
  'xs:simpleContent' => SimpleContent,
75
- 'xs:extension' => Extension
92
+ 'xs:complexContent' => ComplexContent,
93
+ 'xs:extension' => Extension,
94
+ 'xs:import' => Import,
95
+ 'xs:simpleType' => SimpleType
76
96
  }
77
97
 
78
98
  return class_mapping[n.is_a?(Nokogiri::XML::Node) ? n.name : n]
@@ -82,41 +102,40 @@ module XsdReader
82
102
  fullname = [node.namespace ? node.namespace.prefix : nil, node.name].reject{|str| str.nil? || str == ''}.join(':')
83
103
  klass = class_for(fullname)
84
104
  # logger.debug "node_to_object, klass: #{klass.to_s}, fullname: #{fullname}"
85
- klass.nil? ? nil : klass.new(options.merge(:node => node))
105
+ klass.nil? ? nil : klass.new(options.merge(:node => node, :schema => schema))
86
106
  end
87
107
 
88
-
89
108
  #
90
109
  # Child objects
91
110
  #
92
111
 
93
- def map_children(xml_name, klass = nil)
94
- klass ||= class_for(xml_name)
95
- node.search("./#{xml_name}").map{|node| klass.new(options.merge(:node => node))}
112
+ def mappable_children(xml_name)
113
+ node.search("./#{xml_name}").to_a
114
+ end
115
+
116
+ def map_children(xml_name)
117
+ # puts "Map Children with #{xml_name} for #{self.class.to_s}"
118
+ mappable_children(xml_name).map{|current_node| node_to_object(current_node)}
96
119
  end
97
120
 
98
121
  def direct_elements
99
- map_children("xs:element")
122
+ @direct_elements ||= map_children("xs:element")
100
123
  end
101
124
 
102
125
  def elements
103
126
  direct_elements
104
127
  end
105
128
 
106
- def unordered_elements
107
- direct_elements + (complex_type ? complex_type.all_elements : []) + sequences.map(&:all_elements).flatten + choices.map(&:all_elements).flatten
108
- end
109
-
110
129
  def ordered_elements
111
130
  # loop over each interpretable child xml node, and if we can convert a child node
112
131
  # to an XsdReader object, let it give its compilation of all_elements
113
132
  nodes.map{|node| node_to_object(node)}.compact.map do |obj|
114
- obj.is_a?(Element) ? obj : obj.all_elements
133
+ obj.is_a?(Element) ? obj : obj.ordered_elements
115
134
  end.flatten
116
135
  end
117
136
 
118
137
  def all_elements
119
- ordered_elements + (linked_complex_type ? linked_complex_type.ordered_elements : [])
138
+ @all_elements ||= ordered_elements + (linked_complex_type ? linked_complex_type.ordered_elements : [])
120
139
  end
121
140
 
122
141
  def child_elements?
@@ -124,91 +143,109 @@ module XsdReader
124
143
  end
125
144
 
126
145
  def attributes
127
- map_children('xs:attribute')
146
+ @attributes ||= map_children('xs:attribute')
128
147
  end
129
148
 
130
149
  def sequences
131
- map_children("xs:sequence",)
150
+ @sequences ||= map_children("xs:sequence",)
132
151
  end
133
152
 
134
153
  def choices
135
- map_children("xs:choice")
154
+ @choices ||= map_children("xs:choice")
136
155
  end
137
156
 
138
157
  def complex_types
139
- map_children("xs:complexType")
158
+ @complex_types ||= map_children("xs:complexType")
140
159
  end
141
160
 
142
161
  def complex_type
143
- complex_types.first
162
+ complex_types.first || linked_complex_type
144
163
  end
145
164
 
146
165
  def linked_complex_type
147
- complex_type_by_name(type) || complex_type_by_name(type_name)
166
+ @linked_complex_type ||= (schema_for_namespace(type_namespace) || schema).complex_types.find{|ct| ct.name == (type_name || type)}
167
+ #@linked_complex_type ||= object_by_name('xs:complexType', type) || object_by_name('xs:complexType', type_name)
148
168
  end
149
169
 
150
170
  def simple_contents
151
- map_children("xs:simpleContent")
171
+ @simple_contents ||= map_children("xs:simpleContent")
152
172
  end
153
173
 
154
174
  def simple_content
155
175
  simple_contents.first
156
176
  end
157
177
 
178
+ def complex_contents
179
+ @complex_contents ||= map_children("xs:complexContent")
180
+ end
181
+
182
+ def complex_content
183
+ complex_contents.first
184
+ end
185
+
158
186
  def extensions
159
- map_children("xs:extension")
187
+ @extensions ||= map_children("xs:extension")
160
188
  end
161
189
 
162
190
  def extension
163
191
  extensions.first
164
192
  end
165
193
 
194
+ def simple_types
195
+ @simple_types ||= map_children("xs:simpleType")
196
+ end
197
+
198
+ def linked_simple_type
199
+ @linked_simple_type ||= object_by_name('xs:simpleType', type) || object_by_name('xs:simpleType', type_name)
200
+ # @linked_simple_type ||= (type_namespace ? schema_for_namespace(type_namespace) : schema).simple_types.find{|st| st.name == (type_name || type)}
201
+ end
202
+
166
203
  #
167
204
  # Related objects
168
205
  #
169
206
 
170
207
  def parent
171
208
  if node && node.respond_to?(:parent) && node.parent
209
+
172
210
  return node_to_object(node.parent)
173
211
  end
174
212
 
175
213
  nil
176
214
  end
177
215
 
178
- # def ancestors
179
- # result = [parent]
180
-
181
- # while result.first != nil
182
- # result.unshift (result.first.respond_to?(:parent) ? result.first.parent : nil)
183
- # end
184
-
185
- # result.compact
186
- # end
187
-
188
216
  def schema
189
- p = node.parent
190
-
191
- while p.name != 'schema' && !p.nil?
192
- p = p.parent
193
- end
194
- p.nil? ? nil : node_to_object(p)
217
+ return options[:schema] if options[:schema]
218
+ schema_node = node.search('//xs:schema')[0]
219
+ return schema_node.nil? ? nil : node_to_object(schema_node)
195
220
  end
196
221
 
197
- def complex_type_by_name(name)
198
- ct = node.search("//xs:complexType[@name=\"#{name}\"]").first
199
- ct.nil? ? nil : ComplexType.new(options.merge(:node => ct))
200
- end
222
+ def object_by_name(xml_name, name)
223
+ # find in local schema, then look in imported schemas
224
+ nod = node.search("//#{xml_name}[@name=\"#{name}\"]").first
225
+ return node_to_object(nod) if nod
201
226
 
202
- def elements_by_type(type_name)
203
- els = schema.node.search("//xs:element[@type=\"#{type_name}\"]")
227
+ # try to find in any of the importers
228
+ self.schema.imports.each do |import|
229
+ if obj = import.reader.schema.object_by_name(xml_name, name)
230
+ return obj
231
+ end
232
+ end
204
233
 
205
- schema.node.search("//xs:element[@type=\"#{type_name}\"]")
234
+ return nil
235
+ end
206
236
 
207
- while els.length == 0
237
+ def schema_for_namespace(_namespace)
238
+ logger.debug "Shared#schema_for_namespace with _namespace: #{_namespace}"
239
+ return schema if schema.targets_namespace?(_namespace)
208
240
 
241
+ if import = schema.import_by_namespace(_namespace)
242
+ logger.debug "Shared#schema_for_namespace found import schema"
243
+ return import.reader.schema
209
244
  end
210
- end
211
245
 
246
+ logger.debug "Shared#schema_for_namespace no result"
247
+ return nil
248
+ end
212
249
  end
213
250
 
214
251
  end # module XsdReader
@@ -0,0 +1,7 @@
1
+ module XsdReader
2
+
3
+ class SimpleType
4
+ include Shared
5
+ end # class SimpleType
6
+
7
+ end # module XsdReader
@@ -4,7 +4,19 @@ require 'nokogiri'
4
4
  module XsdReader
5
5
 
6
6
  class XML
7
- include Shared
7
+ attr_reader :options
8
+
9
+ def initialize(_opts = {})
10
+ @options = _opts || {}
11
+ raise "#{self.class.to_s}.new expects a hash parameter" if !@options.is_a?(Hash)
12
+ end
13
+
14
+ def logger
15
+ return @logger if @logger
16
+ @logger ||= options[:logger] || Logger.new(STDOUT)
17
+ @logger.level = Logger::WARN
18
+ return @logger
19
+ end
8
20
 
9
21
  def xsd_from_uri
10
22
  # @xsd_from_uri ||= options[:xsd_uri].nil ? nil : open(options[:xsd_uri])
@@ -22,17 +34,38 @@ module XsdReader
22
34
  @doc ||= Nokogiri.XML(xml)
23
35
  end
24
36
 
37
+ def node
38
+ nil
39
+ end
40
+
25
41
  def schema_node
26
42
  doc.root.name == 'schema' ? doc.root : nil
27
43
  end
28
44
 
29
45
  def schema
30
- node_to_object(schema_node)
46
+ @schema ||= Schema.new(self.options.merge(:node => schema_node, :logger => logger))
47
+ end
48
+
49
+ # forwards most functions to schema
50
+ def [](*args)
51
+ schema[*args]
31
52
  end
32
53
 
33
54
  def elements
34
55
  schema.elements
35
56
  end
57
+
58
+ def imports
59
+ schema.imports
60
+ end
61
+
62
+ def simple_types
63
+ schema.simple_types
64
+ end
65
+
66
+ def schema_for_namespace(_ns)
67
+ schema.schema_for_namespace(_ns)
68
+ end
36
69
  end # class XML
37
70
 
38
71
  end # module XsdReader
data/lib/xsd_reader.rb CHANGED
@@ -7,5 +7,8 @@ require 'xsd_reader/sequence'
7
7
  require 'xsd_reader/choice'
8
8
  require 'xsd_reader/complex_type'
9
9
  require 'xsd_reader/simple_content'
10
+ require 'xsd_reader/complex_content'
10
11
  require 'xsd_reader/extension'
12
+ require 'xsd_reader/import'
13
+ require 'xsd_reader/simple_type'
11
14
 
@@ -1,33 +1,33 @@
1
1
  require File.dirname(__FILE__) + '/spec_helper'
2
2
 
3
3
  describe XsdReader do
4
- before :all do
5
- @reader ||= XsdReader::XML.new(:xsd_file => File.expand_path(File.join(File.dirname(__FILE__), 'examples', 'ddex-ern-v36.xsd')))
6
- end
4
+ let(:reader){
5
+ XsdReader::XML.new(:xsd_file => File.expand_path(File.join(File.dirname(__FILE__), 'examples', 'ddex-v36', 'ddex-ern-v36.xsd')))
6
+ }
7
7
 
8
8
  describe XsdReader::Attribute do
9
- before :each do
10
- @attribute = @reader['NewReleaseMessage']['@MessageSchemaVersionId']
11
- end
9
+ let(:attribute){
10
+ reader['NewReleaseMessage']['@MessageSchemaVersionId']
11
+ }
12
12
 
13
13
  it "gives a name" do
14
- expect(@attribute.name).to eq 'MessageSchemaVersionId'
14
+ expect(attribute.name).to eq 'MessageSchemaVersionId'
15
15
  end
16
16
 
17
17
  it "gives a type information" do
18
- expect(@attribute.type).to eq 'xs:string'
19
- expect(@attribute.type_name).to eq 'string'
20
- expect(@attribute.type_namespace).to eq 'xs'
18
+ expect(attribute.type).to eq 'xs:string'
19
+ expect(attribute.type_name).to eq 'string'
20
+ expect(attribute.type_namespace).to eq 'xs'
21
21
  end
22
22
 
23
23
  it "gives a boolean required indication" do
24
- expect(@attribute.required?).to eq true
24
+ expect(attribute.required?).to eq true
25
25
  end
26
26
  end
27
27
 
28
28
  describe "[] operator" do
29
29
  it "gives attribute objects through the square brackets ([]) operator" do
30
- attribute = @reader['NewReleaseMessage']['MessageHeader']['@LanguageAndScriptCode']
30
+ attribute = reader['NewReleaseMessage']['MessageHeader']['@LanguageAndScriptCode']
31
31
  expect(attribute.type).to eq 'xs:string'
32
32
  expect(attribute.required?).to eq false
33
33
  end