xml_schema_mapper 0.0.1 → 0.0.5

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.
@@ -6,29 +6,34 @@ module XmlSchemaMapper
6
6
  class Element
7
7
  attr_reader :xsd
8
8
 
9
- delegate :name, :namespace, :type, to: :xsd
9
+ delegate :name, :namespace, :type, :base, :array?, :required?, to: :xsd
10
10
 
11
- def initialize(xsd_element, klass=nil)
12
- @xsd = xsd_element
13
- @klass = klass
14
- end
15
-
16
- def klass
17
- @klass ||= begin
18
- name = (klass_name || base_klass_name)
19
- name ? (name+"Mapper").constantize : XmlSchemaMapper.default_class
20
- rescue NameError
21
- name.constantize
22
- rescue NameError => e
23
- raise e, "NotImplimented type #{name.inspect}"
24
- end
11
+ def initialize(xsd_element)
12
+ @xsd = xsd_element
25
13
  end
26
14
 
27
15
  def simple?
28
16
  [
29
17
  LibXML::XML::Schema::Types::XML_SCHEMA_TYPE_SIMPLE,
30
18
  LibXML::XML::Schema::Types::XML_SCHEMA_TYPE_BASIC
31
- ].include? @xsd.type.kind
19
+ ].include? type.kind
20
+ end
21
+
22
+ def complex?
23
+ !simple?
24
+ end
25
+
26
+ def elements
27
+ xsd.elements.values.map { |e| XmlSchemaMapper::Element.new(e) }
28
+ end
29
+
30
+ def accept(visitor, data, *args)
31
+ case data
32
+ when Array
33
+ visitor.visit_array(self, data, *args)
34
+ else
35
+ visitor.visit(self, data, *args)
36
+ end
32
37
  end
33
38
 
34
39
  def content_from(object)
@@ -37,42 +42,42 @@ module XmlSchemaMapper
37
42
  end
38
43
 
39
44
  def method_name
40
- name.underscore
45
+ name.underscore.to_sym
41
46
  end
42
47
 
43
48
  def writer
44
- "#{method_name}="
49
+ :"#{method_name}="
45
50
  end
46
51
 
47
52
  def reader
48
- "#{method_name}"
53
+ method_name
49
54
  end
50
55
 
51
- def xpath(xml)
52
- if namespace
53
- xml.at_xpath("./foo:#{name}", { 'foo' => @xsd.namespace })
54
- else
55
- xml.at_xpath("./#{name}")
56
- end
56
+ def converter_class
57
+ (klass_name + "Converter").safe_constantize
57
58
  end
58
59
 
59
- def elements
60
- @xsd.elements.keys.map(&:to_sym)
60
+ def mapper_class
61
+ mapper_class_name.constantize
61
62
  end
62
63
 
63
- private
64
+ def mapper_class_name
65
+ klass_name + "Mapper"
66
+ end
64
67
 
65
68
  def klass_name
66
- @xsd.type.name.camelize
67
- rescue NameError
68
- nil
69
+ if type.name.nil?
70
+ name.underscore.camelize
71
+ else
72
+ type.name.underscore.camelize
73
+ end
69
74
  end
70
75
 
71
76
  def base_klass_name
72
- @xsd.base.name.camelize
73
- rescue NameError
77
+ base.name.camelize
78
+ rescue NoMethodError
74
79
  nil
75
80
  end
76
81
 
77
82
  end
78
- end
83
+ end
@@ -0,0 +1,33 @@
1
+ module XmlSchemaMapper
2
+ class NamespaceResolver
3
+ class Namespace
4
+ attr_reader :prefix, :href
5
+
6
+ def initialize(prefix, href)
7
+ @prefix, @href = prefix, href
8
+ end
9
+ end
10
+
11
+ def initialize(fallback)
12
+ @fallback = fallback
13
+ end
14
+
15
+ # @return [DuckyTyped with #prefix and #href]
16
+ def find_by_href(href)
17
+ by_href(href) || @fallback.find_by_href(href)
18
+ end
19
+
20
+ # @return [DuckyTyped with #prefix and #href]
21
+ def find_by_prefix(prefix)
22
+ by_prefix(prefix) || @fallback.find_by_prefix(prefix)
23
+ end
24
+
25
+ # @return [DuckyTyped with #prefix and #href]
26
+ def by_href(href)
27
+ end
28
+
29
+ # @return [DuckyTyped with #prefix and #href]
30
+ def by_prefix(prefix)
31
+ end
32
+ end
33
+ end
@@ -1,30 +1,90 @@
1
1
  module XmlSchemaMapper
2
2
  class Parser
3
3
  def initialize(klass)
4
- @klass = klass
4
+ @klass = klass
5
+ @module = resolve_module
5
6
  end
6
7
 
7
8
  def parse(xml)
8
9
  instance = @klass.new
9
- xml = document(xml)
10
+ xml = document(xml)
10
11
  @klass.elements.each do |e|
11
- if e.simple?
12
- instance.send(e.writer, content_for(e, xml))
12
+ if e.array?
13
+ write_array_element(e, instance, xml)
13
14
  else
14
- instance.send(e.writer, e.klass.parse(e.xpath(xml)))
15
+ write_element(e, instance, xml)
15
16
  end
16
17
  end
17
18
  instance
18
19
  end
19
20
 
20
- def content_for(element, xml)
21
- node = element.xpath(xml)
21
+ def write_element(element, instance, xml)
22
+ node = get_node_by_xpath(element, xml)
23
+ if element.simple?
24
+ instance.send(element.writer, content_for(node)) if instance.respond_to?(element.writer)
25
+ else
26
+ instance.send(element.writer, resolve_element_parser(element).parse(node)) if instance.respond_to?(element.writer)
27
+ end
28
+ end
29
+
30
+ def write_array_element(element, instance, xml)
31
+ instance.send(element.writer, [])
32
+ if element.simple?
33
+ get_nodes_by_xpath(element, xml).each do |node|
34
+ instance.send(element.reader) << content_for(node) if instance.respond_to?(element.writer)
35
+ end
36
+ else
37
+ get_nodes_by_xpath(element, xml).each do |node|
38
+ instance.send(element.reader) << resolve_element_parser(element).parse(node) if instance.respond_to?(element.writer)
39
+ end
40
+ end
41
+ end
42
+
43
+ def content_for(node)
22
44
  node.content if node
23
45
  end
24
46
 
47
+ def get_node_by_xpath(element, xml)
48
+ if element.namespace
49
+ xml.at_xpath("./foo:#{element.name}", { 'foo' => element.namespace })
50
+ else
51
+ xml.at_xpath("./#{element.name}")
52
+ end
53
+ end
54
+
55
+ def get_nodes_by_xpath(element, xml)
56
+ if element.namespace
57
+ xml.xpath("./foo:#{element.name}", { 'foo' => element.namespace })
58
+ else
59
+ xml.xpath("./#{element.name}")
60
+ end
61
+ end
62
+
25
63
  def document(xml)
26
64
  return xml if xml.is_a?(Nokogiri::XML::Node)
27
65
  Nokogiri::XML(xml).root
28
66
  end
67
+
68
+ private
69
+
70
+ def resolve_element_parser(element)
71
+ parsers_for_resolve(element).find(&:safe_constantize).safe_constantize or raise_class_not_found(e)
72
+ end
73
+
74
+ def parsers_for_resolve(element)
75
+ %W(#{element.mapper_class_name} #{element.klass_name} #{@klass.name}::#{element.klass_name})
76
+ end
77
+
78
+ def raise_class_not_found(element)
79
+ raise NameError, "No one of the classes #{parsers_for_resolve(element)} cannot be found"
80
+ end
81
+
82
+ def resolve_module
83
+ if @klass.name.index('::')
84
+ @klass.name[0..@klass.name.index('::')-1].safe_constantize
85
+ else
86
+ Object
87
+ end
88
+ end
29
89
  end
30
- end
90
+ end
@@ -0,0 +1,84 @@
1
+ module XmlSchemaMapper
2
+ class TestBuilder
3
+ module Helper
4
+ def build_mapper(klass)
5
+ XmlSchemaMapper::TestBuilder.new(klass).build
6
+ end
7
+
8
+ def build_described_mapper
9
+ build_mapper described_class
10
+ end
11
+ end
12
+
13
+ def initialize(klass)
14
+ @klass = klass
15
+ @module = resolve_module
16
+ end
17
+
18
+ def build
19
+ instance = @klass.new
20
+ @klass.elements.each do |e|
21
+ if e.array?
22
+ fill_array_element(e, instance)
23
+ else
24
+ fill_element(e, instance)
25
+ end
26
+ end
27
+ instance
28
+ end
29
+
30
+ def fill_element(element, instance)
31
+ if element.simple?
32
+ instance.send(element.writer, content_for(element)) if instance.respond_to?(element.writer)
33
+ else
34
+ instance.send(element.writer, resolve_element_builder(element).build) if instance.respond_to?(element.writer)
35
+ end
36
+ end
37
+
38
+ def fill_array_element(element, instance)
39
+ instance.send(element.writer, [])
40
+ if element.simple?
41
+ 3.times do
42
+ instance.send(element.reader) << content_for(element) if instance.respond_to?(element.writer)
43
+ end
44
+ else
45
+ 3.times do
46
+ instance.send(element.reader) << resolve_element_builder(element).build if instance.respond_to?(element.writer)
47
+ end
48
+ end
49
+ end
50
+
51
+ def content_for(element)
52
+ case element.type.kind
53
+ when LibXML::XML::Schema::Types::XML_SCHEMA_TYPE_SIMPLE
54
+ element.type.base.name
55
+ when LibXML::XML::Schema::Types::XML_SCHEMA_TYPE_BASIC
56
+ element.type.name
57
+ else
58
+ "content"
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def resolve_element_builder(element)
65
+ TestBuilder.new(parsers_for_resolve(element).find(&:safe_constantize).safe_constantize) or raise_class_not_found(e)
66
+ end
67
+
68
+ def parsers_for_resolve(element)
69
+ %W(#{element.mapper_class_name} #{element.klass_name} #{@klass.name}::#{element.klass_name})
70
+ end
71
+
72
+ def raise_class_not_found(element)
73
+ raise NameError, "No one of the classes #{parsers_for_resolve(element)} cannot be found"
74
+ end
75
+
76
+ def resolve_module
77
+ if @klass.name.index('::')
78
+ @klass.name[0..@klass.name.index('::')-1].safe_constantize
79
+ else
80
+ Object
81
+ end
82
+ end
83
+ end
84
+ end
@@ -1,3 +1,3 @@
1
1
  module XmlSchemaMapper
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.5"
3
3
  end
@@ -7,15 +7,15 @@ describe "Build XML" do
7
7
 
8
8
  context "valid data" do
9
9
  let(:object) do
10
- o = Options.new
10
+ o = OptionsMapper.new
11
11
  o.one = 'one'
12
12
  o.two = 'two'
13
13
 
14
- f = Filter.new
14
+ f = FilterMapper.new
15
15
  f.age = 50
16
16
  f.gender = 'male'
17
17
 
18
- g = GetFirstName.new
18
+ g = GetFirstNameMapper.new
19
19
  g.user_identifier = '001'
20
20
  g.is_out = 'a'
21
21
  g.zone = 'b'
data/spec/builder_spec.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe XmlSchemaMapper::Builder do
4
- let(:options) { o = Options.new; o.one = 'one'; o.two = 'two'; o }
4
+ let(:options) { o = OptionsMapper.new; o.one = 'one'; o.two = 'two'; o }
5
5
  let(:object) do
6
- f = Filter.new; f.age = 50; f.gender = 'male'
7
- g = GetFirstName.new; g.user_identifier = '001'; g.is_out = 'a'; g.zone = 'b'; g.filter = f; g.options = options
6
+ f = FilterMapper.new; f.age = 50; f.gender = 'male'
7
+ g = GetFirstNameMapper.new; g.user_identifier = '001'; g.is_out = 'a'; g.zone = 'b'; g.filter = f; g.options = options
8
8
  g
9
9
  end
10
10
 
@@ -43,7 +43,7 @@ describe XmlSchemaMapper::Builder do
43
43
 
44
44
  it "should add namespace to root node" do
45
45
  namespace = stub(prefix: 'w', href: 'namespace')
46
- element.stub(namespace: 'namespace')
46
+ element.stub(reader: 'is_out', namespace: 'namespace')
47
47
  subject.stub_chain('schema.namespaces.find_by_href').and_return(namespace)
48
48
 
49
49
  subject.build
@@ -98,8 +98,6 @@ describe XmlSchemaMapper::Builder do
98
98
  subject.build
99
99
 
100
100
  element_node.namespace.should be_nil
101
- one_node.namespace.should be_nil
102
- two_node.namespace.should be_nil
103
101
  end
104
102
 
105
103
  it "should accept not a XmlSchemaMapper for node" do
@@ -114,12 +112,29 @@ describe XmlSchemaMapper::Builder do
114
112
  subject.build
115
113
  end
116
114
 
115
+ it "if array build element for each" do
116
+ object.stub(array_mappers: [object.filter,object.filter])
117
+ complex_element.stub(namespace: 'http://example.com/common/', reader: 'array_mappers')
118
+ subject.stub(elements: [complex_element])
119
+
120
+ subject.parent.should_receive(:<<).with(instance_of(Nokogiri::XML::NodeSet))
121
+ subject.build
122
+ end
123
+
124
+ it "if array build element for each" do
125
+ mapper = ArrayNamespace::ArrayOfStringsMapper.new
126
+ mapper.item = [1,2,3,4]
127
+ subj = XmlSchemaMapper::Builder.new(mapper, document.root)
128
+ subj.build
129
+ document.root.search('.//t:item', 't' => 'http://example.com/UserService/type/').count.should eql 4
130
+ end
131
+
117
132
  it "should raise error when don't respond to :to_xml'" do
118
- object.stub(not_a_mapper: [])
133
+ object.stub(not_a_mapper: 1)
119
134
  complex_element.stub(namespace: nil, reader: 'not_a_mapper')
120
135
  subject.stub(elements: [complex_element])
121
136
 
122
- expect { subject.build }.to raise_error("object of GetFirstName#not_a_mapper should respond to :to_xml")
137
+ expect { subject.build }.to raise_error("object of GetFirstNameMapper#not_a_mapper should respond to :to_xml")
123
138
  end
124
139
 
125
140
  end
@@ -1,9 +1,9 @@
1
1
  <?xml version="1.0" encoding="utf-8"?>
2
2
  <xs:schema xmlns:tns="http://example.com/UserService/type/" xmlns:xs="http://www.w3.org/2001/XMLSchema"
3
3
  xmlns:com="http://example.com/common/"
4
- targetNamespace="http://example.com/UserService/type/" version="1.0">
4
+ targetNamespace="http://example.com/UserService/type/" version="1.0" elementFormDefault="qualified">
5
5
 
6
- <xs:import namespace="http://example.com/common/" schemaLocation="common.xsd"/>
6
+ <xs:import namespace="http://example.com/common/" schemaLocation="common.xsd"/>
7
7
 
8
8
  <xs:complexType name="Filter">
9
9
  <xs:sequence>
@@ -24,6 +24,7 @@
24
24
  <xs:element name="options" type="com:Options"/>
25
25
  </xs:sequence>
26
26
  <xs:attribute name="id" type="xs:string"/>
27
+ <xs:attribute name="default" type="xs:string" use="required"/>
27
28
  </xs:complexType>
28
29
 
29
30
  <xs:complexType name="GetLastName">
@@ -43,6 +44,24 @@
43
44
  </xs:complexContent>
44
45
  </xs:complexType>
45
46
 
47
+ <xs:complexType name="ArrayOfGetFirstName">
48
+ <xs:annotation>
49
+ <xs:documentation>Array</xs:documentation>
50
+ </xs:annotation>
51
+ <xs:sequence>
52
+ <xs:element name="query" type="tns:GetFirstName" maxOccurs="unbounded"/>
53
+ </xs:sequence>
54
+ </xs:complexType>
55
+
56
+ <xs:complexType name="ArrayOfStrings">
57
+ <xs:annotation>
58
+ <xs:documentation>Array</xs:documentation>
59
+ </xs:annotation>
60
+ <xs:sequence>
61
+ <xs:element name="item" type="xs:string" maxOccurs="unbounded"/>
62
+ </xs:sequence>
63
+ </xs:complexType>
64
+
46
65
  <xs:element name="gender">
47
66
  <xs:simpleType>
48
67
  <xs:restriction base="xs:string">
@@ -63,5 +82,7 @@
63
82
 
64
83
  <xs:element name="filter" type="tns:Filter"/>
65
84
  <xs:element name="getFirstName" type="tns:GetFirstName"/>
85
+ <xs:element name="getFirstNames" type="tns:ArrayOfGetFirstName"/>
66
86
  <xs:element name="getLastName" type="tns:GetLastName"/>
87
+ <xs:element name="getTypes" type="tns:ArrayOfStrings"/>
67
88
  </xs:schema>