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.
data/.gitignore CHANGED
@@ -15,3 +15,4 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ .idea
@@ -1,13 +1,20 @@
1
- <%- if options[:module_name].present? -%>
2
- class <%= options[:module_name] %>::<%= name.camelize %>Converter
1
+ # encoding: utf-8
2
+ # @note <%= type.annotation.to_s.gsub("\n", "\n# ") %>
3
+
4
+ <%- elements.each do |element| -%>
5
+ <%- (element.annotation || element.type.annotation).each_line do |line| -%>
6
+ # <%= line %>
7
+ <%- end -%>
8
+ # @attr <%= element.name %> [<%= element.type.name %>]
9
+ <%- end -%>
10
+
11
+ <%- if options[:converter_module_name].present? -%>
12
+ class <%= options[:converter_module_name] %>::<%= converter_name %> < Converter
3
13
  <% else -%>
4
- class <%= name.camelize %>Converter
14
+ class <%= converter_name %> < Converter
5
15
  <% end -%>
6
- include ModelConverter
7
16
 
8
- converter do
9
- <%- attributes.each do |attr| -%>
10
- convert_attr :<%= attr.underscore %>
11
- <%- end %>
12
- end
13
- end
17
+ <%- elements.each do |element| -%>
18
+ map mapper: :<%= element.name.underscore %>, model: :<%= element.name.underscore %>
19
+ <%- end -%>
20
+ end
@@ -1,11 +1,12 @@
1
1
  require 'spec_helper'
2
2
 
3
- <%- if options[:module_name].present? -%>
4
- describe <%= options[:module_name] %>::<%= name.camelize %> do
3
+ <%- elements.each do |element| -%>
4
+ # @attr <%= element.name %> [<%= element.type.name %>]
5
+ <%- end %>
6
+
7
+ <%- if options[:converter_module_name].present? -%>
8
+ describe <%= options[:converter_module_name] %>::<%= converter_name %> do
5
9
  <% else -%>
6
- describe <%= name.camelize %> do
10
+ describe <%= converter_name %> do
7
11
  <% end -%>
8
- it { should be_a XmlSchemaMapper }
9
- it { _schema.should be_a LibXML::XML::Schema }
10
- it { _type.name.should eql '<%= name %>' }
11
- end
12
+ end
@@ -1,16 +1,35 @@
1
1
  # encoding: utf-8
2
+ <%- unless type.annotation.to_s.blank? -%>
2
3
  # @note <%= type.annotation.to_s.gsub("\n", "\n# ") %>
3
-
4
- <%- elements.each do |element| -%>
5
- # @attr <%= element.name %> [<%= element.type.name %>] <%= element.annotation || element.type.annotation %>
6
4
  <%- end -%>
7
5
 
8
- <%- if options[:module_name].present? -%>
9
- class <%= options[:module_name] %>::<%= name.camelize %>Mapper
10
- <% else -%>
11
- class <%= name.camelize %>Mapper
12
- <% end -%>
6
+ class <%= mapper_name %>
13
7
  include XmlSchemaMapper
14
8
  schema '<%= schema_path %>'
15
9
  type '<%= name %>'
16
- end
10
+
11
+ <%- elements.each do |element| -%>
12
+ <%- element_annotation(element).split("\n").delete_if(&:blank?).each do |comment| -%>
13
+ # <%= comment %>
14
+ <%- end -%>
15
+ # minOccurs: <%= element.min_occurs %>, maxOccurs: <%= element.max_occurs %>
16
+ attr_accessor :<%= element.name.underscore %>
17
+ <%- end -%>
18
+
19
+ <%- subclasses.each do |subclass, type| -%>
20
+ class <%= subclass.split('::').map(&:camelize).join('::') %>
21
+ include XmlSchemaMapper
22
+ schema '<%= schema_path %>'
23
+ annonymus_type '<%= [name.camelize, subclass].join('::') %>'
24
+
25
+ <%- type.elements.values.each do |element| -%>
26
+ <%- element_annotation(element).split("\n").delete_if(&:blank?).each do |comment| -%>
27
+ # <%= comment %>
28
+ <%- end -%>
29
+ # minOccurs: <%= element.min_occurs %>, maxOccurs: <%= element.max_occurs %>
30
+ attr_accessor :<%= element.name.underscore %>
31
+ <%- end -%>
32
+ end
33
+
34
+ <%- end unless subclasses.empty? -%>
35
+ end
@@ -1,16 +1,22 @@
1
- # encoding: utf-8
2
- # <%= type.annotation.to_s.gsub("\n", "\n# ") %>
1
+ require 'spec_helper'
2
+ require "xml_schema_mapper/test_builder"
3
3
 
4
4
  <%- elements.each do |element| -%>
5
- # @attr <%= element.name %> [<%= element.type.name %>] <%= element.type.annotation %>
5
+ # @attr <%= element.name %> [<%= element.type.name %>]
6
6
  <%- end %>
7
7
 
8
- require 'spec_helper'
9
-
10
8
  <%- if options[:module_name].present? -%>
11
- describe <%= options[:module_name] %>::<%= name.camelize %> do
9
+ describe <%= options[:module_name] %>::<%= name.camelize %>Mapper do
12
10
  <% else -%>
13
- describe <%= name.camelize %> do
11
+ describe <%= name.underscore.camelize %>Mapper do
14
12
  <% end -%>
15
- it { should be_a ModelConverter }
16
- end
13
+
14
+ include XmlSchemaMapper::TestBuilder::Helper
15
+
16
+ subject { build_described_mapper }
17
+
18
+ it "should be self-compatible xml <-> object" do
19
+ described_class.parse(subject.to_xml).to_xml.should eql subject.to_xml
20
+ end
21
+
22
+ end
@@ -4,19 +4,15 @@ require "thor/group"
4
4
  module XsdMappers
5
5
  class CLI < Thor
6
6
 
7
- desc 'mappers path/to/xsd path/to/generate/classes', 'generate classes for xsd types'
7
+ desc 'generate path/to/xsd', 'generate mappers for xsd types'
8
8
  method_option :module_name, default: ''
9
- def mappers(schema_path, destination = './')
10
- schema(schema_path).types.each do |name, type|
11
- Mappers.new([name, schema_path, destination]).invoke_all
12
- end
13
- end
9
+ method_option :converter_module_name, default: ''
10
+ method_option :'skip-converters', type: :boolean
14
11
 
15
- desc 'converters path/to/xsd path/to/generate/classes', 'generate converters for xsd types'
16
- method_option :module_name, default: ''
17
- def converters(schema_path, destination = './')
18
- schema(schema_path).types.each do |name, type|
19
- Converters.new([name, schema_path, destination], options.merge(attributes: type.elements.keys)).invoke_all
12
+ def generate(schema_path)
13
+ schema(schema_path).types.each do |name, _|
14
+ Mappers.new([name, schema_path], options).invoke_all
15
+ Converters.new([name, schema_path], options).invoke_all unless options[:'skip-converters']
20
16
  end
21
17
  end
22
18
 
@@ -34,7 +30,6 @@ module XsdMappers
34
30
  # Define arguments and options
35
31
  argument :name
36
32
  argument :schema_path
37
- argument :destination, default: './'
38
33
  class_option :module_name, default: ''
39
34
  class_option :override, default: false
40
35
  class_option :base_type, default: nil
@@ -44,20 +39,27 @@ module XsdMappers
44
39
  File.dirname(__FILE__)
45
40
  end
46
41
 
42
+ def module_path
43
+ options[:module_name] ? options[:module_name].underscore : ""
44
+ end
45
+
47
46
  def create_lib_file
48
- template('templates/mapper_class.erb', "app/#{destination}/#{name.underscore}_mapper.rb")
47
+ filename = "#{name.underscore}_mapper.rb"
48
+ template('templates/mapper_class.erb', File.join('app/mappers/', module_path, filename))
49
49
  end
50
50
 
51
51
  def create_test_file
52
- test = options[:test_framework] == "test" ? :test : :spec
52
+ test = options[:test_framework] == "test" ? :test : :spec
53
+ filename = "#{name.underscore}_mapper_#{test}.rb"
53
54
  with_padding do
54
- template 'templates/mapper_spec.erb', "spec/#{destination}/#{name.underscore}_mapper_#{test}.rb"
55
+ template 'templates/mapper_spec.erb', File.join("#{test}/mappers/", module_path, filename)
55
56
  end
56
57
  end
57
58
 
58
59
  protected
59
- def attributes
60
- options[:attributes]
60
+
61
+ def subclasses
62
+ type.annonymus_subtypes_recursively.inject({ }, &:merge)
61
63
  end
62
64
 
63
65
  def schema
@@ -71,21 +73,64 @@ module XsdMappers
71
73
  def type
72
74
  @type ||= schema.types[name]
73
75
  end
76
+
77
+ def element_annotation(element)
78
+ type = element.type.name || "#{element.name}"
79
+ comment = element.annotation || element.type.annotation
80
+
81
+ if is_a_simple?(element.type)
82
+ "#{comment}\n" <<
83
+ "@return [#{type.camelize}]"
84
+ else
85
+ "#{comment}\n" <<
86
+ "@return [#{module_name}#{type.camelize}Mapper]"
87
+ end
88
+ end
89
+
90
+ def is_a_simple?(type)
91
+ [LibXML::XML::Schema::Types::XML_SCHEMA_TYPE_SIMPLE,
92
+ LibXML::XML::Schema::Types::XML_SCHEMA_TYPE_BASIC].include?(type.kind)
93
+ end
94
+
95
+ def module_name
96
+ if options[:module_name].presence
97
+ "#{options[:module_name]}::"
98
+ else
99
+ ""
100
+ end
101
+ end
102
+
103
+ def mapper_name
104
+ class_name = "#{name.underscore.camelize}Mapper"
105
+ "#{module_name}#{class_name}"
106
+ end
74
107
  end
75
108
 
76
109
  class Converters < Mappers
77
110
  class_option :attributes, type: :array
111
+ class_option :converter_module_name, default: ""
78
112
  class_option :force, default: false
113
+ class_option :skip, default: true
114
+
115
+ def module_path
116
+ options[:converter_module_name] ? options[:converter_module_name].underscore : ""
117
+ end
79
118
 
80
119
  def create_lib_file
81
- template('templates/converter_class.erb', "app/#{destination}/#{name.underscore}_converter.rb")
120
+ filename = "#{name.underscore}_converter.rb"
121
+ template('templates/converter_class.erb', File.join('app/converters/', module_path, filename))
82
122
  end
83
123
 
84
124
  def create_test_file
85
- test = options[:test_framework] == "test" ? :test : :spec
125
+ test = options[:test_framework] == "test" ? :test : :spec
126
+ filename = "#{name.underscore}_converter_#{test}.rb"
86
127
  with_padding do
87
- template 'templates/converter_spec.erb', "spec/#{destination}/#{name.underscore}_converter_#{test}.rb"
128
+ template 'templates/converter_spec.erb', File.join("#{test}/converters/", module_path, filename)
88
129
  end
89
130
  end
131
+
132
+ def converter_name
133
+ "#{name.underscore.camelize}Converter"
134
+ end
90
135
  end
91
- end
136
+ end
@@ -4,46 +4,52 @@ require "nokogiri"
4
4
  require "active_support/concern"
5
5
  require "active_support/core_ext/class"
6
6
  require "active_support/core_ext/module/delegation"
7
+ require "active_support/core_ext/object/blank"
8
+ require "active_support/core_ext/string/inflections"
7
9
  require "virtus"
8
10
 
9
11
  require "xml_schema_mapper/element"
10
12
  require "xml_schema_mapper/parser"
11
13
  require "xml_schema_mapper/builder"
14
+ require "xml_schema_mapper/namespace_resolver"
12
15
 
13
16
  module XmlSchemaMapper
14
17
  extend ActiveSupport::Concern
15
- mattr_accessor :default_class
16
- self.default_class = String
18
+ mattr_accessor :namespace_resolver_class
17
19
 
18
20
  included do
19
21
  class_attribute :_schema
20
22
  class_attribute :_type
21
- class_attribute :elements
22
- self.elements = []
23
+ class_attribute :namespace_resolver_class
23
24
  include Virtus
24
25
  end
25
26
 
26
27
  module ClassMethods
27
- def schema(location)
28
- self._schema = LibXML::XML::Schema.cached(location)
28
+ def schema(location=nil)
29
+ if location
30
+ self._schema = LibXML::XML::Schema.cached(location)
31
+ else
32
+ self._schema
33
+ end
29
34
  end
30
35
 
31
- def type(name)
36
+ def type(name=nil)
32
37
  raise(%Q{call "schema 'path/to/your/File.xsd'" before calling "type"}) unless _schema
33
- self._type = _schema.types[name]
34
- define_elements!
38
+ if name
39
+ self._type = _schema.types[name]
40
+ attr_accessor :attrs
41
+ else
42
+ self._type
43
+ end
35
44
  end
36
45
 
37
- def define_elements!
38
- _type.elements.values.each do |element|
39
- e = XmlSchemaMapper::Element.new(element)
40
- elements << e
41
- begin
42
- attr_accessor e.method_name
43
- rescue
44
- raise [e.method_name, e.klass, e.type.name].inspect
45
- end
46
- end
46
+ def annonymus_type(name)
47
+ raise(%Q{call "schema 'path/to/your/File.xsd'" before calling "type"}) unless _schema
48
+ path = name.split('::')
49
+ type = _schema.types[path.shift]
50
+ self._type = path.map do |n|
51
+ type = type.elements[n].type if type
52
+ end.last
47
53
  end
48
54
 
49
55
  def parse(string_or_node)
@@ -58,20 +64,90 @@ module XmlSchemaMapper
58
64
  raise(ArgumentError, "param must be a String or Nokogiri::XML::Node, but \"#{string_or_node.inspect}\" given")
59
65
  end
60
66
  end
67
+
68
+ def elements
69
+ @elements ||= type.elements.values.map do |element|
70
+ XmlSchemaMapper::Element.new(element)
71
+ end
72
+ end
73
+
74
+ def attrs
75
+ @attrs ||= type.attributes
76
+ end
77
+ end
78
+
79
+ delegate :first, :last, :each, :length, :size, :all?, :any?, :one?, :empty?, to: :element_values
80
+
81
+ def [](key)
82
+ send(key)
83
+ end
84
+
85
+ def []=(key, val)
86
+ send(:"#{key}=", val)
87
+ end
88
+
89
+ def accept(visitor, *args)
90
+ visitor.visit(self, *args)
91
+ end
92
+
93
+ def simple?
94
+ _type.simple?
95
+ end
96
+
97
+ def values
98
+ _type.facets.map &:value
99
+ end
100
+
101
+ def type
102
+ self.class.type
103
+ end
104
+
105
+ def schema
106
+ self.class.schema
61
107
  end
62
108
 
63
109
  def element_names
64
- elements.map(&:name)
110
+ elements.keys
65
111
  end
66
112
 
67
- def to_xml(options = {})
68
- xml_document.root.to_xml({:encoding => 'UTF-8'}.merge(options))
113
+ def element_values
114
+ elements.values
115
+ end
116
+
117
+ def elements
118
+ type.elements.values.inject({ }) do |hash, element|
119
+ hash.merge element.name.underscore.to_sym => send(element.name.underscore.to_sym)
120
+ end
121
+ end
122
+
123
+ def to_xml(options = { })
124
+ xml_document.root.to_xml({ :encoding => 'UTF-8' }.merge(options))
69
125
  end
70
126
 
71
127
  def xml_document
72
128
  document = XmlSchemaMapper::Builder.create_document(_type)
73
- builder = XmlSchemaMapper::Builder.new(self, document.root)
129
+
130
+ ns = namespace_resolver.find_by_href (global_element || _type).namespace
131
+ document.root.namespace = document.root.add_namespace_definition(ns.prefix, ns.href)
132
+
133
+ builder = XmlSchemaMapper::Builder.new(self, document.root, namespace_resolver)
74
134
  builder.build
75
135
  builder.document
76
136
  end
137
+
138
+ def namespace_resolver
139
+ case
140
+ when self.class.namespace_resolver_class
141
+ self.class.namespace_resolver_class.new(schema.namespaces)
142
+ when XmlSchemaMapper.namespace_resolver_class
143
+ XmlSchemaMapper.namespace_resolver_class.new(schema.namespaces)
144
+ else
145
+ schema.namespaces
146
+ end
147
+ end
148
+
149
+ def global_element
150
+ schema.elements.values.find { |e| e.type.name == _type.name }
151
+ end
152
+
77
153
  end
@@ -4,29 +4,39 @@ end
4
4
  module XmlSchemaMapper
5
5
  class Builder
6
6
 
7
- attr_reader :document, :parent
8
-
9
- delegate :elements, :to => :@klass
7
+ attr_reader :document, :parent, :namespace_resolver
8
+
9
+ def initialize(source, parent, namespace_resolver)
10
+ @parent = parent.is_a?(Nokogiri::XML::Document) ? parent.root : parent
11
+ @document = parent.document
12
+ @source = source
13
+ @klass = source.class
14
+ @namespace_resolver = namespace_resolver
15
+ @schema = @klass._schema
16
+ end
10
17
 
11
- def initialize(source, parent)
12
- @parent = parent.is_a?(Nokogiri::XML::Document) ? parent.root : parent
13
- @document = parent.document
14
- @source = source
15
- @klass = source.class
16
- @schema = @klass._schema
18
+ def elements
19
+ @klass.type.elements.values.map { |e| XmlSchemaMapper::Element.new e }
17
20
  end
18
21
 
19
22
  def build
20
23
  elements.each do |element|
21
24
  add_element_namespace_to_root!(element)
22
25
  node = create_node(element)
23
- parent << node unless node.content.blank?
26
+
27
+ parent << node if can_add?(element, node)
28
+
29
+ if node.is_a?(Nokogiri::XML::NodeSet)
30
+ node.each { |n| n.namespace = nil if element.namespace.nil? }
31
+ else
32
+ node.namespace = nil if element.namespace.nil?
33
+ end
24
34
  end
25
35
  self
26
36
  end
27
37
 
28
- def clean!
29
-
38
+ def can_add?(element, node)
39
+ node.is_a?(Nokogiri::XML::NodeSet) || node.content.present?
30
40
  end
31
41
 
32
42
  private
@@ -41,50 +51,82 @@ module XmlSchemaMapper
41
51
 
42
52
  def setup_namespace(element)
43
53
  yield.tap do |node|
44
- return if node.nil?
45
- node.namespace = find_namespace_definition(element.namespace)
54
+ case node
55
+ when Nokogiri::XML::NodeSet
56
+ node.each { |n| n.namespace = find_namespace_definition(element.namespace) }
57
+ when NilClass
58
+ else
59
+ node.namespace = find_namespace_definition(element.namespace)
60
+ end
46
61
  end
47
62
  end
48
63
 
49
64
  def simple_node(element)
50
- document.create_element(element.name, element.content_from(source))
65
+ value = source.send(element.reader)
66
+ if value.is_a? Array
67
+ list = value.map do |i|
68
+ document.create_element(element.name, i)
69
+ end
70
+ Nokogiri::XML::NodeSet.new(document, list)
71
+ else
72
+ document.create_element(element.name, element.content_from(source))
73
+ end
51
74
  end
52
75
 
53
76
  def complex_node(element)
54
77
  object = source.send(element.reader)
55
78
  complex_root_node = document.create_element(element.name)
56
79
 
80
+ complex_children(complex_root_node, object)
81
+ rescue NoToXmlError
82
+ raise("object of #{source.class}##{element.reader} should respond to :to_xml")
83
+ end
84
+
85
+ def complex_children(complex_root_node, object)
57
86
  if object.is_a?(XmlSchemaMapper)
58
87
  complex_node_from_mapper(complex_root_node, object)
59
88
  else
60
89
  complex_node_from_xml(complex_root_node, object)
61
90
  end
62
- rescue NoToXmlError
63
- raise("object of #{source.class}##{element.reader} should respond to :to_xml")
64
91
  end
65
92
 
66
- def complex_node_from_xml(root, object)
67
- return root if object.nil?
68
- if object.respond_to?(:to_xml)
69
- root << object.to_xml
93
+ def complex_node_from_xml(complex_root_node, object)
94
+ return complex_root_node if object.nil?
95
+ if object.is_a?(Array)
96
+ list = object.map do |o|
97
+ complex_node_item = complex_root_node.dup
98
+ complex_children(complex_node_item, o)
99
+ end
100
+ Nokogiri::XML::NodeSet.new(complex_root_node.document, list)
101
+ elsif object.respond_to?(:to_xml)
102
+ complex_root_node << object.to_xml
70
103
  else
71
104
  raise NoToXmlError
72
105
  end
73
106
  end
74
107
 
75
- def complex_node_from_mapper(root, object)
76
- XmlSchemaMapper::Builder.build(object, root).parent
108
+ def complex_node_from_mapper(complex_root_node, object)
109
+ XmlSchemaMapper::Builder.build(object, complex_root_node, namespace_resolver).parent
77
110
  end
78
111
 
79
112
  def add_element_namespace_to_root!(element)
80
113
  if element.namespace
81
- ns = schema.namespaces.find_by_href(element.namespace)
114
+
115
+ ns = find_namespace(element)
82
116
  raise "prefix for namespace #{element.namespace.inspect} not found" unless ns
83
117
 
84
118
  document.root.add_namespace(ns.prefix, ns.href)
85
119
  end
86
120
  end
87
121
 
122
+ def find_namespace(element)
123
+ if namespace_resolver
124
+ namespace_resolver.find_by_href(element.namespace)
125
+ else
126
+ schema.namespaces.find_by_href(element.namespace)
127
+ end
128
+ end
129
+
88
130
  def find_namespace_definition(href)
89
131
  document.root.namespace_definitions.find { |ns| ns.href == href }
90
132
  end
@@ -97,9 +139,9 @@ module XmlSchemaMapper
97
139
  end
98
140
  end
99
141
 
100
- def self.build(mapper, parent)
101
- XmlSchemaMapper::Builder.new(mapper, parent).build
142
+ def self.build(mapper, parent, namespace_resolver)
143
+ XmlSchemaMapper::Builder.new(mapper, parent, namespace_resolver).build
102
144
  end
103
145
 
104
146
  end
105
- end
147
+ end