roundtrip_xml 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/roundtrip/base_cleanroom.rb +82 -0
- data/lib/roundtrip/dsl_builder.rb +38 -0
- data/lib/roundtrip/dsl_runtime.rb +53 -0
- data/lib/roundtrip/plain_accessors.rb +17 -0
- data/lib/roundtrip/root_cleanroom.rb +25 -0
- data/lib/roundtrip/roxml_builder.rb +88 -0
- data/lib/roundtrip_xml.rb +7 -0
- metadata +107 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c1d89e8b67e0e4e6d17b36a6f7bd669f848978a0
|
4
|
+
data.tar.gz: 3c9dabb9fad98650def6f2befaab68adfd56fccf
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 53589a94506c4e41bc5bcb8f478929c003aec8f92f4040c4f871524a5daeeaa6b702ffaa167041d8ea4cf42b057c07f355031e88ead55a63e203f71b9e9fa816
|
7
|
+
data.tar.gz: 80edb85109be632bd9d1ad6b3003ab7931c679b04f7b489518677d48228e91a39664e0aa3cafe3696c0f5e737d4871f031aa2e02603b893d8aa02a46486b95cf
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'cleanroom'
|
2
|
+
# Generates cleanroom methods corresponding to all the xml_accessors and plain_accessors of @el
|
3
|
+
class BaseCleanroom
|
4
|
+
include Cleanroom
|
5
|
+
def get_el
|
6
|
+
@el
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(el, runtime)
|
10
|
+
@runtime = runtime
|
11
|
+
@el = el
|
12
|
+
get_el.attributes.each do |attr|
|
13
|
+
method_name = attr.accessor.to_sym
|
14
|
+
create_method(method_name) do |name = nil, &block|
|
15
|
+
if !block.nil?
|
16
|
+
clazz = name ? @runtime.fetch(name) : attr.sought_type
|
17
|
+
value = expand(clazz, &block)
|
18
|
+
elsif name
|
19
|
+
value = name
|
20
|
+
else
|
21
|
+
return get_el.send(attr.accessor.to_sym)
|
22
|
+
end
|
23
|
+
|
24
|
+
if attr.array?
|
25
|
+
array_attr = get_el.send(attr.accessor.to_sym)
|
26
|
+
array_attr ||= attr.default
|
27
|
+
array_attr << value
|
28
|
+
get_el.send(attr.setter.to_sym, array_attr)
|
29
|
+
else
|
30
|
+
get_el.send(attr.setter.to_sym, value) unless get_el.send(attr.accessor.to_sym)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
self.class.send(:expose, method_name)
|
34
|
+
end
|
35
|
+
|
36
|
+
expose_attr_accessors
|
37
|
+
end
|
38
|
+
|
39
|
+
def expose_attr_accessors()
|
40
|
+
get_el.class.plain_accessors.each do |a|
|
41
|
+
create_method(a) do |value = nil|
|
42
|
+
return get_el.send(a) unless value
|
43
|
+
get_el.send("#{a}=".to_sym, value) if value
|
44
|
+
end
|
45
|
+
self.class.send(:expose, a)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
def create_method(name, &block)
|
49
|
+
self.class.send(:define_method, name, &block)
|
50
|
+
end
|
51
|
+
|
52
|
+
def expand(clazz, &block)
|
53
|
+
plain_accessors = @el.class.plain_accessors
|
54
|
+
hash = {}
|
55
|
+
@value_holder ||= {}
|
56
|
+
if plain_accessors != 0
|
57
|
+
hash = plain_accessors.inject({}) {|h, a| h[a] = @el.send(a); h}
|
58
|
+
end
|
59
|
+
child = @runtime.create_cleanroom(clazz)
|
60
|
+
child.inherit_properties @value_holder.merge(hash)
|
61
|
+
# child.inherit_properties(@args, @value_holder) if instance_variable_defined?(:@args)
|
62
|
+
child.evaluate &block
|
63
|
+
child.evaluate &child.get_el.process if child.get_el.respond_to? :process
|
64
|
+
# evaluate the ROXML object's proc, which further modifies the element
|
65
|
+
# child.evaluate &child.get_el.class.proc if child.respond_to? :proc
|
66
|
+
child.get_el
|
67
|
+
end
|
68
|
+
|
69
|
+
def inherit_properties(props)
|
70
|
+
@value_holder = props
|
71
|
+
props.each do |name, val|
|
72
|
+
# @value_holder.class.send(:attr_accessor, arg)
|
73
|
+
# @value_holder.send("#{arg}=".to_sym, value_holder.send(arg))
|
74
|
+
# create_method(arg) do
|
75
|
+
# @value_holder.send(arg)
|
76
|
+
# end
|
77
|
+
self.class.send(:attr_reader, name)
|
78
|
+
self.instance_variable_set("@#{name}".to_sym, val)
|
79
|
+
self.class.send(:expose, name)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
class DslBuilder
|
4
|
+
def initialize(xmlStr, runtime, root_class)
|
5
|
+
@doc = Nokogiri::XML(xmlStr)
|
6
|
+
@runtime = runtime
|
7
|
+
@root_class = root_class
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_dsl
|
11
|
+
write_attrs @runtime.fetch(@root_class), @doc.root
|
12
|
+
end
|
13
|
+
|
14
|
+
def write_attrs(clazz, xml, inset='')
|
15
|
+
clazz.roxml_attrs.inject('') do |out, attr|
|
16
|
+
accessor = attr.accessor
|
17
|
+
selector = attr.name
|
18
|
+
|
19
|
+
if attr.sought_type == :text
|
20
|
+
out += inset + "#{accessor} '#{xml.xpath(selector)[0].content}'\n"
|
21
|
+
elsif attr.sought_type == :attr
|
22
|
+
out += inset + "#{accessor} '#{xml.xpath("@#{selector}")[0].content}'\n"
|
23
|
+
elsif !attr.array?
|
24
|
+
out += inset + "#{accessor} do\n#{write_attrs attr.sought_type, xml.xpath(selector)[0], inset + ' '}#{inset}end\n"
|
25
|
+
|
26
|
+
else
|
27
|
+
xml.xpath(selector).each do |node|
|
28
|
+
out += inset + "#{accessor} do\n#{write_attrs attr.sought_type, node, inset + ' '}#{inset}end\n"
|
29
|
+
if inset == ''
|
30
|
+
out += "\n"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
out
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'roxml'
|
2
|
+
require 'nokogiri'
|
3
|
+
require './lib/roundtrip/roxml_builder'
|
4
|
+
require './lib/roundtrip/root_cleanroom'
|
5
|
+
require './lib/roundtrip/base_cleanroom'
|
6
|
+
# Class which evaluates DSL and read XML files to populate the namespace with classes
|
7
|
+
class DslRuntime
|
8
|
+
def initialize()
|
9
|
+
@classes = {}
|
10
|
+
end
|
11
|
+
def populate(files)
|
12
|
+
files.each {|f| populate_from_file f }
|
13
|
+
end
|
14
|
+
|
15
|
+
def populate_from_file (file)
|
16
|
+
populate_raw File.read(file)
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
def populate_raw (raw)
|
21
|
+
builder = RoxmlBuilder.new (Nokogiri::XML(raw).root), @classes
|
22
|
+
new_classes = builder.build_classes
|
23
|
+
@classes.merge! new_classes
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
def fetch(class_name)
|
28
|
+
@classes[class_name]
|
29
|
+
end
|
30
|
+
|
31
|
+
def add_class(name, clazz)
|
32
|
+
@classes[name] = clazz
|
33
|
+
end
|
34
|
+
|
35
|
+
def evaluate_file(path, root_class)
|
36
|
+
cleanroom = RootCleanroom.new(fetch(root_class).new, self)
|
37
|
+
cleanroom.evaluate_file path
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
def evaluate_raw(dsl, root_class)
|
42
|
+
cleanroom = RootCleanroom.new(fetch(root_class).new, self)
|
43
|
+
cleanroom.evaluate dsl
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
def fetch_cleanroom(root_class)
|
48
|
+
BaseCleanroom.new(fetch(root_class).new, self)
|
49
|
+
end
|
50
|
+
def create_cleanroom(root_class)
|
51
|
+
BaseCleanroom.new(root_class.new, self)
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module PlainAccessors
|
2
|
+
def self.included(base)
|
3
|
+
base.extend ClassMethods
|
4
|
+
end
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
def plain_accessor(name)
|
8
|
+
@plain_accessors ||= []
|
9
|
+
@plain_accessors << name
|
10
|
+
attr_accessor name
|
11
|
+
end
|
12
|
+
|
13
|
+
def plain_accessors
|
14
|
+
@plain_accessors || []
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require './lib/roundtrip/base_cleanroom'
|
2
|
+
# addes the `define` and `use_file` method to a cleanroom. Only used when evaluating a root file
|
3
|
+
class RootCleanroom < BaseCleanroom
|
4
|
+
|
5
|
+
def define(name, parent, *params, &block)
|
6
|
+
new_class = Class.new(@runtime.fetch parent) do
|
7
|
+
# simply using @@params is referring to `AContainer`
|
8
|
+
self.class_variable_set(:@@block, block)
|
9
|
+
params.each do |param|
|
10
|
+
plain_accessor param
|
11
|
+
end
|
12
|
+
|
13
|
+
def process
|
14
|
+
self.class.class_variable_get(:@@block)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
@runtime.add_class name, new_class
|
18
|
+
end
|
19
|
+
expose(:define)
|
20
|
+
|
21
|
+
def use_file(path)
|
22
|
+
self.evaluate_file(path)
|
23
|
+
end
|
24
|
+
expose(:use_file)
|
25
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# require 'roxml'
|
2
|
+
require 'nokogiri'
|
3
|
+
require './lib/roundtrip/plain_accessors'
|
4
|
+
# Builds dynamic classes based on an XML file.
|
5
|
+
# Classes that already exist in the DslRuntime instance are modified if necessary, not overridden.
|
6
|
+
class RoxmlBuilder
|
7
|
+
def initialize (root, current_classes = {})
|
8
|
+
# @type Nokogiri::Element
|
9
|
+
@root = root
|
10
|
+
|
11
|
+
# @type Map<Symbol, ROXML>
|
12
|
+
@generated_classes = current_classes
|
13
|
+
|
14
|
+
@root_class = @generated_classes[name_to_sym(root.name)] || Class.new do
|
15
|
+
include ROXML
|
16
|
+
include PlainAccessors
|
17
|
+
xml_convention :dasherize
|
18
|
+
xml_name root.name
|
19
|
+
def attributes
|
20
|
+
self.class.roxml_attrs
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
@generated_classes[ name_to_sym(@root.name)] = @root_class
|
25
|
+
end
|
26
|
+
def build_classes
|
27
|
+
# @root_class.xml_name (@root.name)
|
28
|
+
@root.xpath("//#{@root.name}/*|//#{@root.name}/@*").each do |child|
|
29
|
+
default_opts = {from:child.name}
|
30
|
+
if is_leaf_element?(child)
|
31
|
+
add_accessor name_to_sym(child.name, true), default_opts, @root
|
32
|
+
elsif child.type == Nokogiri::XML::Node::ATTRIBUTE_NODE
|
33
|
+
add_accessor name_to_sym(child.name, true), {from: "@#{child.name}"}, @root
|
34
|
+
else
|
35
|
+
builder = RoxmlBuilder.new child, @generated_classes
|
36
|
+
new_classes = builder.build_classes
|
37
|
+
child_name = name_to_sym(child.name, true)
|
38
|
+
child_class_name = name_to_sym child.name
|
39
|
+
add_accessor child_name, default_opts.merge({as: new_classes[child_class_name]}), @root
|
40
|
+
@generated_classes.merge!(new_classes)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
@generated_classes
|
45
|
+
end
|
46
|
+
|
47
|
+
def name_to_sym(name, lower_case = false)
|
48
|
+
# name.gsub('-', '_').to_sym
|
49
|
+
new_name = name.split('-').collect(&:capitalize).join
|
50
|
+
new_name[0] = new_name[0].downcase! if lower_case
|
51
|
+
new_name.to_sym
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
def add_accessor(name, opts = {}, node = nil)
|
56
|
+
attrs = @root_class.roxml_attrs
|
57
|
+
attr = attrs.find do |a|
|
58
|
+
a.accessor.to_sym == name
|
59
|
+
end
|
60
|
+
# if class already has xml attribute, delete the old version and add the new version
|
61
|
+
|
62
|
+
if attr && node
|
63
|
+
if node.xpath("./#{attr.name}").size > 1
|
64
|
+
@root_class.instance_variable_set(:@roxml_attrs, attrs.select {|i| i != attr })
|
65
|
+
new_attr_type = opts[:as]
|
66
|
+
# add a new attribute with the array type.
|
67
|
+
@root_class.xml_accessor name, opts.merge({as: [new_attr_type]})
|
68
|
+
end
|
69
|
+
# attr_type = attr.sought_type
|
70
|
+
# new_attr_type = opts[:as]
|
71
|
+
# if new_attr_type && attr_type != :text && new_attr_type.tag_name == attr_type.tag_name
|
72
|
+
# # remove `attr` from the class's attributes
|
73
|
+
# @root_class.instance_variable_set(:@roxml_attrs, attrs.select {|i| i != attr })
|
74
|
+
# # add a new attribute with the array type.
|
75
|
+
# @root_class.xml_accessor name, opts.merge({as: [new_attr_type]})
|
76
|
+
# end
|
77
|
+
else
|
78
|
+
@root_class.xml_accessor name, opts
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# element is a leaf it has text content and no attributes
|
83
|
+
def is_leaf_element?(element)
|
84
|
+
element.type == Nokogiri::XML::Node::ELEMENT_NODE &&
|
85
|
+
element.attributes.size == 0 &&
|
86
|
+
element.children.select {|c| c.type == Nokogiri::XML::Node::ELEMENT_NODE}.empty?
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
require './lib/roundtrip/dsl_runtime'
|
2
|
+
require './lib/roundtrip/dsl_builder'
|
3
|
+
require './lib/roundtrip/base_cleanroom'
|
4
|
+
require './lib/roundtrip/root_cleanroom'
|
5
|
+
require './lib/roundtrip/roxml_builder'
|
6
|
+
require './lib/roundtrip/plain_accessors'
|
7
|
+
puts DslRuntime.new
|
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: roundtrip_xml
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Chris Usick
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-03-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: roxml
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.3'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: nokogiri
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.6'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.6'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: cleanroom
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: nokogiri-diff
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.2'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.2'
|
69
|
+
description: A DSL that reads existing XML, generating an internal schema and using
|
70
|
+
it to create a (somewhat) type safe DSL
|
71
|
+
email: chris.usick@northfieldit.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- lib/roundtrip/base_cleanroom.rb
|
77
|
+
- lib/roundtrip/dsl_builder.rb
|
78
|
+
- lib/roundtrip/dsl_runtime.rb
|
79
|
+
- lib/roundtrip/plain_accessors.rb
|
80
|
+
- lib/roundtrip/root_cleanroom.rb
|
81
|
+
- lib/roundtrip/roxml_builder.rb
|
82
|
+
- lib/roundtrip_xml.rb
|
83
|
+
homepage: https://github.com/chrisUsick/roundtrip-xml
|
84
|
+
licenses:
|
85
|
+
- MIT
|
86
|
+
metadata: {}
|
87
|
+
post_install_message:
|
88
|
+
rdoc_options: []
|
89
|
+
require_paths:
|
90
|
+
- lib
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
requirements: []
|
102
|
+
rubyforge_project:
|
103
|
+
rubygems_version: 2.4.6
|
104
|
+
signing_key:
|
105
|
+
specification_version: 4
|
106
|
+
summary: DSL which learns from XML
|
107
|
+
test_files: []
|