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.
- data/.gitignore +1 -0
- data/lib/thor/templates/converter_class.erb +17 -10
- data/lib/thor/templates/converter_spec.erb +8 -7
- data/lib/thor/templates/mapper_class.erb +28 -9
- data/lib/thor/templates/mapper_spec.erb +15 -9
- data/lib/thor/xsd_mappers.rb +66 -21
- data/lib/xml_schema_mapper.rb +99 -23
- data/lib/xml_schema_mapper/builder.rb +69 -27
- data/lib/xml_schema_mapper/element.rb +39 -34
- data/lib/xml_schema_mapper/namespace_resolver.rb +33 -0
- data/lib/xml_schema_mapper/parser.rb +68 -8
- data/lib/xml_schema_mapper/test_builder.rb +84 -0
- data/lib/xml_schema_mapper/version.rb +1 -1
- data/spec/build_xml_spec.rb +3 -3
- data/spec/builder_spec.rb +23 -8
- data/spec/fixtures/UserService.xsd +23 -2
- data/spec/fixtures/common.xsd +1 -1
- data/spec/fixtures/get_first_name.xml +8 -8
- data/spec/fixtures/instance1.xml +8 -8
- data/spec/fixtures/instance_with_arrays.xml +29 -0
- data/spec/fixtures/user_service.rb +113 -4
- data/spec/parse_xml_spec.rb +51 -9
- data/spec/visitor_spec.rb +167 -0
- data/spec/xml_schema_mapper_spec.rb +2 -2
- metadata +40 -42
- data/.idea/.rakeTasks +0 -7
- data/.idea/encodings.xml +0 -5
- data/.idea/misc.xml +0 -8
- data/.idea/modules.xml +0 -9
- data/.idea/scopes/scope_settings.xml +0 -5
- data/.idea/vcs.xml +0 -7
- data/.idea/workspace.xml +0 -459
- data/.idea/xml_schema_mapper.iml +0 -29
@@ -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
|
12
|
-
@xsd
|
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?
|
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
|
-
|
53
|
+
method_name
|
49
54
|
end
|
50
55
|
|
51
|
-
def
|
52
|
-
|
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
|
60
|
-
|
60
|
+
def mapper_class
|
61
|
+
mapper_class_name.constantize
|
61
62
|
end
|
62
63
|
|
63
|
-
|
64
|
+
def mapper_class_name
|
65
|
+
klass_name + "Mapper"
|
66
|
+
end
|
64
67
|
|
65
68
|
def klass_name
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
-
|
73
|
-
rescue
|
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
|
4
|
+
@klass = klass
|
5
|
+
@module = resolve_module
|
5
6
|
end
|
6
7
|
|
7
8
|
def parse(xml)
|
8
9
|
instance = @klass.new
|
9
|
-
xml
|
10
|
+
xml = document(xml)
|
10
11
|
@klass.elements.each do |e|
|
11
|
-
if e.
|
12
|
-
|
12
|
+
if e.array?
|
13
|
+
write_array_element(e, instance, xml)
|
13
14
|
else
|
14
|
-
|
15
|
+
write_element(e, instance, xml)
|
15
16
|
end
|
16
17
|
end
|
17
18
|
instance
|
18
19
|
end
|
19
20
|
|
20
|
-
def
|
21
|
-
node = element
|
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
|
data/spec/build_xml_spec.rb
CHANGED
@@ -7,15 +7,15 @@ describe "Build XML" do
|
|
7
7
|
|
8
8
|
context "valid data" do
|
9
9
|
let(:object) do
|
10
|
-
o =
|
10
|
+
o = OptionsMapper.new
|
11
11
|
o.one = 'one'
|
12
12
|
o.two = 'two'
|
13
13
|
|
14
|
-
f =
|
14
|
+
f = FilterMapper.new
|
15
15
|
f.age = 50
|
16
16
|
f.gender = 'male'
|
17
17
|
|
18
|
-
g =
|
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 =
|
4
|
+
let(:options) { o = OptionsMapper.new; o.one = 'one'; o.two = 'two'; o }
|
5
5
|
let(:object) do
|
6
|
-
f =
|
7
|
-
g =
|
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
|
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
|
-
|
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>
|