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 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