xml_schema_mapper 0.0.1

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