xml_schema_mapper 0.0.1 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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>