xsd-reader 0.0.1 → 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.
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