xml_schema_mapper 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # XmlSchemaMapper
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'xml_schema_mapper'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install xml_schema_mapper
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift File.expand_path('../../lib', __FILE__)
3
+
4
+ require "xml_schema_mapper"
5
+ require "thor/xsd_mappers"
6
+
7
+ XsdMappers::CLI.start
@@ -0,0 +1,13 @@
1
+ <%- if options[:module_name].present? -%>
2
+ class <%= options[:module_name] %>::<%= name.camelize %>Converter
3
+ <% else -%>
4
+ class <%= name.camelize %>Converter
5
+ <% end -%>
6
+ include ModelConverter
7
+
8
+ converter do
9
+ <%- attributes.each do |attr| -%>
10
+ convert_attr :<%= attr.underscore %>
11
+ <%- end %>
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ <%- if options[:module_name].present? -%>
4
+ describe <%= options[:module_name] %>::<%= name.camelize %> do
5
+ <% else -%>
6
+ describe <%= name.camelize %> do
7
+ <% 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
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+ # @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
+ <%- end -%>
7
+
8
+ <%- if options[:module_name].present? -%>
9
+ class <%= options[:module_name] %>::<%= name.camelize %>Mapper
10
+ <% else -%>
11
+ class <%= name.camelize %>Mapper
12
+ <% end -%>
13
+ include XmlSchemaMapper
14
+ schema '<%= schema_path %>'
15
+ type '<%= name %>'
16
+ end
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+ # <%= type.annotation.to_s.gsub("\n", "\n# ") %>
3
+
4
+ <%- elements.each do |element| -%>
5
+ # @attr <%= element.name %> [<%= element.type.name %>] <%= element.type.annotation %>
6
+ <%- end %>
7
+
8
+ require 'spec_helper'
9
+
10
+ <%- if options[:module_name].present? -%>
11
+ describe <%= options[:module_name] %>::<%= name.camelize %> do
12
+ <% else -%>
13
+ describe <%= name.camelize %> do
14
+ <% end -%>
15
+ it { should be_a ModelConverter }
16
+ end
@@ -0,0 +1,91 @@
1
+ require "thor"
2
+ require "thor/group"
3
+
4
+ module XsdMappers
5
+ class CLI < Thor
6
+
7
+ desc 'mappers path/to/xsd path/to/generate/classes', 'generate classes for xsd types'
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
14
+
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
20
+ end
21
+ end
22
+
23
+ protected
24
+
25
+ def schema(path)
26
+ @schema ||= LibXML::XML::Schema.cached(path)
27
+ end
28
+
29
+ end
30
+
31
+ class Mappers < Thor::Group
32
+ include Thor::Actions
33
+
34
+ # Define arguments and options
35
+ argument :name
36
+ argument :schema_path
37
+ argument :destination, default: './'
38
+ class_option :module_name, default: ''
39
+ class_option :override, default: false
40
+ class_option :base_type, default: nil
41
+ class_option :force, default: true
42
+
43
+ def self.source_root
44
+ File.dirname(__FILE__)
45
+ end
46
+
47
+ def create_lib_file
48
+ template('templates/mapper_class.erb', "app/#{destination}/#{name.underscore}_mapper.rb")
49
+ end
50
+
51
+ def create_test_file
52
+ test = options[:test_framework] == "test" ? :test : :spec
53
+ with_padding do
54
+ template 'templates/mapper_spec.erb', "spec/#{destination}/#{name.underscore}_mapper_#{test}.rb"
55
+ end
56
+ end
57
+
58
+ protected
59
+ def attributes
60
+ options[:attributes]
61
+ end
62
+
63
+ def schema
64
+ @schema ||= LibXML::XML::Schema.cached(schema_path)
65
+ end
66
+
67
+ def elements
68
+ @elements ||= type.elements.values
69
+ end
70
+
71
+ def type
72
+ @type ||= schema.types[name]
73
+ end
74
+ end
75
+
76
+ class Converters < Mappers
77
+ class_option :attributes, type: :array
78
+ class_option :force, default: false
79
+
80
+ def create_lib_file
81
+ template('templates/converter_class.erb', "app/#{destination}/#{name.underscore}_converter.rb")
82
+ end
83
+
84
+ def create_test_file
85
+ test = options[:test_framework] == "test" ? :test : :spec
86
+ with_padding do
87
+ template 'templates/converter_spec.erb', "spec/#{destination}/#{name.underscore}_converter_#{test}.rb"
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,105 @@
1
+ class NoToXmlError < NoMethodError
2
+ end
3
+
4
+ module XmlSchemaMapper
5
+ class Builder
6
+
7
+ attr_reader :document, :parent
8
+
9
+ delegate :elements, :to => :@klass
10
+
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
17
+ end
18
+
19
+ def build
20
+ elements.each do |element|
21
+ add_element_namespace_to_root!(element)
22
+ node = create_node(element)
23
+ parent << node unless node.content.blank?
24
+ end
25
+ self
26
+ end
27
+
28
+ def clean!
29
+
30
+ end
31
+
32
+ private
33
+
34
+ attr_reader :source, :schema
35
+
36
+ def create_node(element)
37
+ setup_namespace(element) do
38
+ element.simple? ? simple_node(element) : complex_node(element)
39
+ end
40
+ end
41
+
42
+ def setup_namespace(element)
43
+ yield.tap do |node|
44
+ return if node.nil?
45
+ node.namespace = find_namespace_definition(element.namespace)
46
+ end
47
+ end
48
+
49
+ def simple_node(element)
50
+ document.create_element(element.name, element.content_from(source))
51
+ end
52
+
53
+ def complex_node(element)
54
+ object = source.send(element.reader)
55
+ complex_root_node = document.create_element(element.name)
56
+
57
+ if object.is_a?(XmlSchemaMapper)
58
+ complex_node_from_mapper(complex_root_node, object)
59
+ else
60
+ complex_node_from_xml(complex_root_node, object)
61
+ end
62
+ rescue NoToXmlError
63
+ raise("object of #{source.class}##{element.reader} should respond to :to_xml")
64
+ end
65
+
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
70
+ else
71
+ raise NoToXmlError
72
+ end
73
+ end
74
+
75
+ def complex_node_from_mapper(root, object)
76
+ XmlSchemaMapper::Builder.build(object, root).parent
77
+ end
78
+
79
+ def add_element_namespace_to_root!(element)
80
+ if element.namespace
81
+ ns = schema.namespaces.find_by_href(element.namespace)
82
+ raise "prefix for namespace #{element.namespace.inspect} not found" unless ns
83
+
84
+ document.root.add_namespace(ns.prefix, ns.href)
85
+ end
86
+ end
87
+
88
+ def find_namespace_definition(href)
89
+ document.root.namespace_definitions.find { |ns| ns.href == href }
90
+ end
91
+
92
+ # CLASS_METHODS
93
+
94
+ def self.create_document(xtype)
95
+ Nokogiri::XML::Document.new.tap do |doc|
96
+ doc.root = doc.create_element(xtype.name.camelize(:lower))
97
+ end
98
+ end
99
+
100
+ def self.build(mapper, parent)
101
+ XmlSchemaMapper::Builder.new(mapper, parent).build
102
+ end
103
+
104
+ end
105
+ end
@@ -0,0 +1,78 @@
1
+ Int = Integer
2
+ Double = Float
3
+ Decimal = Integer
4
+
5
+ module XmlSchemaMapper
6
+ class Element
7
+ attr_reader :xsd
8
+
9
+ delegate :name, :namespace, :type, to: :xsd
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
25
+ end
26
+
27
+ def simple?
28
+ [
29
+ LibXML::XML::Schema::Types::XML_SCHEMA_TYPE_SIMPLE,
30
+ LibXML::XML::Schema::Types::XML_SCHEMA_TYPE_BASIC
31
+ ].include? @xsd.type.kind
32
+ end
33
+
34
+ def content_from(object)
35
+ data = object.send(reader)
36
+ data.respond_to?(:to_xml) ? data.to_xml : data
37
+ end
38
+
39
+ def method_name
40
+ name.underscore
41
+ end
42
+
43
+ def writer
44
+ "#{method_name}="
45
+ end
46
+
47
+ def reader
48
+ "#{method_name}"
49
+ end
50
+
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
57
+ end
58
+
59
+ def elements
60
+ @xsd.elements.keys.map(&:to_sym)
61
+ end
62
+
63
+ private
64
+
65
+ def klass_name
66
+ @xsd.type.name.camelize
67
+ rescue NameError
68
+ nil
69
+ end
70
+
71
+ def base_klass_name
72
+ @xsd.base.name.camelize
73
+ rescue NameError
74
+ nil
75
+ end
76
+
77
+ end
78
+ end
@@ -0,0 +1,30 @@
1
+ module XmlSchemaMapper
2
+ class Parser
3
+ def initialize(klass)
4
+ @klass = klass
5
+ end
6
+
7
+ def parse(xml)
8
+ instance = @klass.new
9
+ xml = document(xml)
10
+ @klass.elements.each do |e|
11
+ if e.simple?
12
+ instance.send(e.writer, content_for(e, xml))
13
+ else
14
+ instance.send(e.writer, e.klass.parse(e.xpath(xml)))
15
+ end
16
+ end
17
+ instance
18
+ end
19
+
20
+ def content_for(element, xml)
21
+ node = element.xpath(xml)
22
+ node.content if node
23
+ end
24
+
25
+ def document(xml)
26
+ return xml if xml.is_a?(Nokogiri::XML::Node)
27
+ Nokogiri::XML(xml).root
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,3 @@
1
+ module XmlSchemaMapper
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,77 @@
1
+ require "xml_schema_mapper/version"
2
+ require "xml"
3
+ require "nokogiri"
4
+ require "active_support/concern"
5
+ require "active_support/core_ext/class"
6
+ require "active_support/core_ext/module/delegation"
7
+ require "virtus"
8
+
9
+ require "xml_schema_mapper/element"
10
+ require "xml_schema_mapper/parser"
11
+ require "xml_schema_mapper/builder"
12
+
13
+ module XmlSchemaMapper
14
+ extend ActiveSupport::Concern
15
+ mattr_accessor :default_class
16
+ self.default_class = String
17
+
18
+ included do
19
+ class_attribute :_schema
20
+ class_attribute :_type
21
+ class_attribute :elements
22
+ self.elements = []
23
+ include Virtus
24
+ end
25
+
26
+ module ClassMethods
27
+ def schema(location)
28
+ self._schema = LibXML::XML::Schema.cached(location)
29
+ end
30
+
31
+ def type(name)
32
+ raise(%Q{call "schema 'path/to/your/File.xsd'" before calling "type"}) unless _schema
33
+ self._type = _schema.types[name]
34
+ define_elements!
35
+ end
36
+
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
47
+ end
48
+
49
+ def parse(string_or_node)
50
+ return if string_or_node.nil?
51
+ case string_or_node
52
+ when String
53
+ string = File.exist?(string_or_node) ? File.read(string_or_node) : string_or_node
54
+ XmlSchemaMapper::Parser.new(self).parse(string)
55
+ when Nokogiri::XML::Node
56
+ XmlSchemaMapper::Parser.new(self).parse(string_or_node)
57
+ else
58
+ raise(ArgumentError, "param must be a String or Nokogiri::XML::Node, but \"#{string_or_node.inspect}\" given")
59
+ end
60
+ end
61
+ end
62
+
63
+ def element_names
64
+ elements.map(&:name)
65
+ end
66
+
67
+ def to_xml(options = {})
68
+ xml_document.root.to_xml({:encoding => 'UTF-8'}.merge(options))
69
+ end
70
+
71
+ def xml_document
72
+ document = XmlSchemaMapper::Builder.create_document(_type)
73
+ builder = XmlSchemaMapper::Builder.new(self, document.root)
74
+ builder.build
75
+ builder.document
76
+ end
77
+ end
@@ -0,0 +1,34 @@
1
+ require "spec_helper"
2
+
3
+ describe "Build XML" do
4
+
5
+ context "by GetFirstName" do
6
+ let(:xml) { File.read('spec/fixtures/get_first_name.xml') }
7
+
8
+ context "valid data" do
9
+ let(:object) do
10
+ o = Options.new
11
+ o.one = 'one'
12
+ o.two = 'two'
13
+
14
+ f = Filter.new
15
+ f.age = 50
16
+ f.gender = 'male'
17
+
18
+ g = GetFirstName.new
19
+ g.user_identifier = '001'
20
+ g.is_out = 'a'
21
+ g.zone = 'b'
22
+ g.filter = f
23
+ g.options = o
24
+
25
+ g
26
+ end
27
+
28
+ it "should build xml" do
29
+ object.to_xml.should eql xml
30
+ end
31
+ end
32
+ end
33
+
34
+ end
@@ -0,0 +1,126 @@
1
+ require "spec_helper"
2
+
3
+ describe XmlSchemaMapper::Builder do
4
+ let(:options) { o = Options.new; o.one = 'one'; o.two = 'two'; o }
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
8
+ g
9
+ end
10
+
11
+ let(:document) { XmlSchemaMapper::Builder.create_document(object._type) }
12
+ subject { XmlSchemaMapper::Builder.new(object, document.root) }
13
+
14
+ context 'after initialize' do
15
+ its(:source) { should be object }
16
+ end
17
+
18
+ context 'on build' do
19
+ let(:element) { stub(namespace: 'http://example.com/common/', name: 'element', simple?: true, content_from: 1) }
20
+ let(:complex_element) { stub(namespace: 'http://example.com/common/', name: 'element', simple?: false) }
21
+ let(:stub_node) { document.create_element('element') }
22
+
23
+ before do
24
+ subject.stub(elements: [element], value: 1)
25
+ end
26
+
27
+ it "should create node for each element" do
28
+ subject.should_receive(:create_node).with(element).and_return(stub_node)
29
+ subject.build
30
+ end
31
+
32
+ it "should create simple_node if element#simple?" do
33
+ element.should_receive(:simple?).and_return(true)
34
+ subject.should_receive(:simple_node).and_return(stub_node)
35
+ subject.build
36
+ end
37
+
38
+ it "should create complex_node unless element#simple?" do
39
+ element.should_receive(:simple?).and_return(false)
40
+ subject.should_receive(:complex_node).and_return(stub_node)
41
+ subject.build
42
+ end
43
+
44
+ it "should add namespace to root node" do
45
+ namespace = stub(prefix: 'w', href: 'namespace')
46
+ element.stub(namespace: 'namespace')
47
+ subject.stub_chain('schema.namespaces.find_by_href').and_return(namespace)
48
+
49
+ subject.build
50
+
51
+ subject.document.root.namespaces.should include "xmlns:w" => "namespace"
52
+ end
53
+
54
+ it "should raise error when element namespace not found" do
55
+ element.stub(namespace: 'namespace')
56
+ subject.stub_chain('schema.namespaces.find_by_href').and_return(nil)
57
+
58
+ expect { subject.build }.to raise_error("prefix for namespace \"namespace\" not found")
59
+ end
60
+
61
+ it "should set namespace for node" do
62
+ subject.stub(simple_node: stub_node)
63
+ subject.build
64
+ stub_node.namespace.prefix.should eql 'cm'
65
+ stub_node.namespace.href.should eql 'http://example.com/common/'
66
+ end
67
+
68
+ it "should create complex element with namespace" do
69
+ complex_element.stub(reader: 'filter')
70
+ element_node = document.create_element('element')
71
+ age_node = document.create_element('age', '50')
72
+ gender_node = document.create_element('gender', 'male')
73
+ subject.stub(elements: [complex_element])
74
+
75
+ subject.document.should_receive(:create_element).with('element').and_return(element_node)
76
+ subject.document.should_receive(:create_element).with('age', 50).and_return(age_node)
77
+ subject.document.should_receive(:create_element).with('gender', 'male').and_return(gender_node)
78
+
79
+ subject.build
80
+
81
+ element_node.namespace.prefix.should eql 'cm'
82
+ gender_node.namespace.prefix.should eql 'tns'
83
+ age_node.namespace.prefix.should eql 'tns'
84
+ end
85
+
86
+ it "should create complex element without namespace" do
87
+ complex_element.stub(namespace: nil, reader: 'options')
88
+ subject.stub(elements: [complex_element])
89
+
90
+ element_node = document.create_element('element')
91
+ one_node = document.create_element('one', 'one')
92
+ two_node = document.create_element('two', 'two')
93
+
94
+ subject.document.should_receive(:create_element).with('element').and_return(element_node)
95
+ subject.document.should_receive(:create_element).with('one', 'one').and_return(one_node)
96
+ subject.document.should_receive(:create_element).with('two', 'two').and_return(two_node)
97
+
98
+ subject.build
99
+
100
+ element_node.namespace.should be_nil
101
+ one_node.namespace.should be_nil
102
+ two_node.namespace.should be_nil
103
+ end
104
+
105
+ it "should accept not a XmlSchemaMapper for node" do
106
+ object.stub(not_a_mapper: document.create_element('not_a_mapper'))
107
+ complex_element.stub(namespace: 'http://example.com/common/', reader: 'not_a_mapper')
108
+ subject.stub(elements: [complex_element])
109
+ element_node = document.create_element('element') << object.not_a_mapper.to_xml
110
+
111
+ element_node.should_receive(:<<).with(object.not_a_mapper.to_xml).and_return(element_node)
112
+ subject.document.should_receive(:create_element).with('element').and_return(element_node)
113
+
114
+ subject.build
115
+ end
116
+
117
+ it "should raise error when don't respond to :to_xml'" do
118
+ object.stub(not_a_mapper: [])
119
+ complex_element.stub(namespace: nil, reader: 'not_a_mapper')
120
+ subject.stub(elements: [complex_element])
121
+
122
+ expect { subject.build }.to raise_error("object of GetFirstName#not_a_mapper should respond to :to_xml")
123
+ end
124
+
125
+ end
126
+ end
@@ -0,0 +1,10 @@
1
+ require "spec_helper"
2
+
3
+ describe XmlSchemaMapper::Element do
4
+
5
+ subject { XmlSchemaMapper::Element.new(@type) }
6
+
7
+ context "resolve klass name" do
8
+ it "should eql"
9
+ end
10
+ end
@@ -0,0 +1,17 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <xs:schema xmlns:tns="http://example.com/UserService/type/" xmlns:xs="http://www.w3.org/2001/XMLSchema"
3
+ targetNamespace="http://example.com/UserService/type/loc" version="1.0">
4
+
5
+ <xs:import namespace="http://example.com/UserService/type/" schemaLocation="UserService.xsd"/>
6
+
7
+
8
+ <xs:element name="getFirstNameObj">
9
+ <xs:complexType>
10
+ <xs:sequence>
11
+ <xs:element ref="tns:getFirstName"/>
12
+ <xs:element ref="tns:getLastName"/>
13
+ </xs:sequence>
14
+ </xs:complexType>
15
+ </xs:element>
16
+
17
+ </xs:schema>