roundtrip_xml 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5849fafbc85b77e0d69f8e666fb448c2ad1e4587
4
- data.tar.gz: ebbb32cf50068e62bf885db90bf2f1e511dc1615
3
+ metadata.gz: b5d0171291e731011fc69e726421dc68b3ceb039
4
+ data.tar.gz: 1aaaa7152a29b616cbfd7e43b003ccf1b625ffa9
5
5
  SHA512:
6
- metadata.gz: e64a54d09659d4954280f0ebd543162a8d53369840b882fcf789aff98aead6b5e3027c53622f39dcc6622b8534d3790d6bd7e9678e132ecfca543a456e212d19
7
- data.tar.gz: bafb364f19e5f0c248feac36ea2a49732124b92057c3e4289995a35fd0cb965db82978d450ef3958308a38d66772cfbe339d164fc9a1d1eb6264e09d29264ed6
6
+ metadata.gz: de71838561d98d183d2e83b2a0311cd3db0c66f89911771ba8ea90a954b9d9ad7d6aa97e14cfd8b5aac6a78a4f5f34c13b56e7825aebac5496f866c63fb6a42a
7
+ data.tar.gz: bf4e7a4d49cccafad9ec1c21a4953bd9ccc9dd7b5eca652b8cdb65daecc0b04deafbcf3b5aa18000fa7342c0dc2babcf28a33ab45db1bfc6685471de0f5e24b3
@@ -4,3 +4,7 @@ require './lib/roundtrip_xml/base_cleanroom'
4
4
  require './lib/roundtrip_xml/root_cleanroom'
5
5
  require './lib/roundtrip_xml/roxml_builder'
6
6
  require './lib/roundtrip_xml/plain_accessors'
7
+ require './lib/roundtrip_xml/utils'
8
+ require './lib/roundtrip_xml/extractor'
9
+ require './lib/roundtrip_xml/roxml_subclass_helpers'
10
+ require './lib/roundtrip_xml/sexp_dsl_builder'
@@ -53,27 +53,20 @@ class BaseCleanroom
53
53
  plain_accessors = @el.class.plain_accessors
54
54
  hash = {}
55
55
  @value_holder ||= {}
56
- if plain_accessors != 0
57
- hash = plain_accessors.inject({}) {|h, a| h[a] = @el.send(a); h}
58
- end
56
+ hash = plain_accessors.inject({}) {|h, a| h[a] = @el.send(a); h}
59
57
  child = @runtime.create_cleanroom(clazz)
60
58
  child.inherit_properties @value_holder.merge(hash)
61
- # child.inherit_properties(@args, @value_holder) if instance_variable_defined?(:@args)
59
+
62
60
  child.evaluate &block
63
61
  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
62
+
66
63
  child.get_el
67
64
  end
68
65
 
69
66
  def inherit_properties(props)
70
67
  @value_holder = props
71
68
  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
69
+
77
70
  self.class.send(:attr_reader, name)
78
71
  self.instance_variable_set("@#{name}".to_sym, val)
79
72
  self.class.send(:expose, name)
@@ -3,26 +3,44 @@ require 'nokogiri'
3
3
  require 'roundtrip_xml/roxml_builder'
4
4
  require 'roundtrip_xml/root_cleanroom'
5
5
  require 'roundtrip_xml/base_cleanroom'
6
+ require 'roundtrip_xml/utils'
7
+ require 'roundtrip_xml/sexp_dsl_builder'
8
+ require 'tree'
9
+ require 'set'
6
10
  # Class which evaluates DSL and read XML files to populate the namespace with classes
7
11
  class DslRuntime
12
+ include Utils
8
13
  def initialize()
9
14
  @classes = {}
15
+ @root_classes = Set.new
10
16
  end
11
- def populate(files)
12
- files.each {|f| populate_from_file f }
17
+ def populate(files, root_method=nil)
18
+ files.map {|f| populate_from_file f, root_method }
13
19
  end
14
20
 
15
- def populate_from_file (file)
16
- populate_raw File.read(file)
21
+ def populate_from_file (file, root_method=nil)
22
+ populate_raw File.read(file), root_method
17
23
 
18
24
  end
19
25
 
20
- def populate_raw (raw)
26
+ def populate_raw (raw, root_method = nil)
21
27
  builder = RoxmlBuilder.new (Nokogiri::XML(raw).root), @classes
22
28
  new_classes = builder.build_classes
29
+ @root_classes << builder.root_class_name
23
30
  @classes.merge! new_classes
24
- end
31
+ if root_method
32
+ roxml_root = fetch(builder.root_class_name).from_xml raw
33
+
34
+ extractor = Extractor.new roxml_root.send(root_method), self
25
35
 
36
+ new_objs = extractor.convert_roxml_objs
37
+ subclasses = extractor.subclasses
38
+ roxml_root.send("#{root_method}=", new_objs)
39
+ builder = SexpDslBuilder.new [roxml_root], subclasses, self
40
+
41
+ builder.write_roxml_objs
42
+ end
43
+ end
26
44
 
27
45
  def fetch(class_name)
28
46
  @classes[class_name]
@@ -47,7 +65,116 @@ class DslRuntime
47
65
  def fetch_cleanroom(root_class)
48
66
  BaseCleanroom.new(fetch(root_class).new, self)
49
67
  end
68
+
50
69
  def create_cleanroom(root_class)
51
70
  BaseCleanroom.new(root_class.new, self)
52
71
  end
72
+
73
+ def marshal_dump
74
+ classes = @classes.inject({}) do |hash, (name, clazz)|
75
+ hash[name] = {xml_name: clazz.tag_name }
76
+ hash[name][:attrs] = clazz.roxml_attrs.map do |accessor|
77
+ type = accessor.sought_type.class == Class ?
78
+ accessor.sought_type.class_name : accessor.sought_type
79
+ from = accessor.sought_type == :attr ? "@#{accessor.name}" : accessor.name
80
+ {
81
+ name: accessor.accessor,
82
+ opts: {
83
+ as: accessor.array? ? [type] : type,
84
+ from: from
85
+ }
86
+ }
87
+ end
88
+ hash
89
+ end
90
+
91
+ {
92
+ root_classes: @root_classes,
93
+ classes: classes
94
+ }
95
+ end
96
+
97
+ def deserialize_class(node, config)
98
+ clazz = fetch(node.name) || new_roxml_class(config[:xml_name])
99
+ config[:attrs].each do |attr|
100
+ type_is_parent = node.parentage && node.parentage.any? {|n| n.name == attr[:opts][:as]}
101
+ if type_is_parent
102
+ add_unprocessed_attr attr, clazz
103
+ else
104
+ clazz.xml_accessor attr[:name], transform_accessor_opts(attr[:opts])
105
+ end
106
+ end
107
+ clazz
108
+ end
109
+
110
+ def add_unprocessed_attr(attr_config, clazz)
111
+ @unprocessed_attrs ||= []
112
+ @unprocessed_attrs << AttrJob.new(attr_config, clazz)
113
+ end
114
+
115
+ def transform_accessor_opts(opts)
116
+ attr_type = opts[:as]
117
+ is_array = attr_type.class == Array
118
+ type = is_array ? attr_type.first : attr_type
119
+ if is_array
120
+ opts[:as] = [fetch(type)]
121
+ else
122
+ opts[:as] = fetch type
123
+ end
124
+ opts
125
+ end
126
+
127
+ def marshal_load(data)
128
+ initialize
129
+ @root_classes.merge data[:root_classes]
130
+ trees = @root_classes.map {|clazz| hash_to_tree data[:classes], clazz}
131
+ trees.each do |tree|
132
+ tree.postordered_each do |node|
133
+ @classes[node.name] = deserialize_class(node, node.content)
134
+ end
135
+ end
136
+ @unprocessed_attrs.each do |job|
137
+ clazz = job.class
138
+ config = job.config
139
+ clazz.xml_accessor config[:name], transform_accessor_opts(config[:opts])
140
+ end if @unprocessed_attrs
141
+ end
142
+
143
+ def hash_to_tree(hash, root_name, processed = [])
144
+ node = Tree::TreeNode.new(root_name, hash[root_name])
145
+ processed << root_name
146
+ children = child_classes(hash, hash[root_name])
147
+ children.each do |name|
148
+ node << hash_to_tree(hash, name, processed) unless processed.include? name
149
+ end
150
+ node
151
+ end
152
+
153
+ def child_classes(hash, config)
154
+ config[:attrs].map do |attr|
155
+ attr_type = attr[:opts][:as]
156
+ type = attr_type.class == Array ? attr_type.first : attr_type
157
+ type if hash.key? type
158
+ end.compact
159
+ end
160
+
161
+
162
+ def serialize(path)
163
+ file = File.new path, 'w'
164
+ Marshal.dump self, file
165
+ file.close
166
+ end
167
+
168
+ def self.load(path)
169
+ file = File.open path, 'r'
170
+ Marshal.load file
171
+ end
172
+ end
173
+
174
+ class AttrJob
175
+ attr_accessor :class, :config
176
+ def initialize(config, clazz)
177
+ self.config = config
178
+ self.class = clazz
179
+ end
53
180
  end
@@ -0,0 +1,81 @@
1
+ require 'roxml'
2
+ require 'nokogiri'
3
+ require 'roundtrip_xml/utils'
4
+ require 'hashdiff'
5
+ require 'set'
6
+ require 'roundtrip_xml/utils'
7
+ require 'multiset'
8
+ require 'roundtrip_xml/roxml_subclass_helpers.rb'
9
+
10
+ class Extractor
11
+ include Utils
12
+ attr_reader :subclasses
13
+ def initialize(roxml_objs, runtime)
14
+ @roxml_objs = roxml_objs
15
+ @hashes = @roxml_objs.map { |obj| obj.to_hash }
16
+ @runtime = runtime
17
+ @subclass_names = Multiset.new
18
+ @subclasses = []
19
+ end
20
+ def list_diffs
21
+
22
+ HashDiff.best_diff(@hashes[0], @hashes[6]).sort_by do |diff|
23
+ m = diff[1].match(/\./)
24
+ m ? m.size : 0
25
+ end.map {|diff| diff[1]}
26
+ end
27
+
28
+ def similar_fields
29
+ keys = Set.new @hashes[0].keys
30
+ diff_keys = Set.new
31
+ @hashes.each do |hash_1|
32
+ @hashes.each do |hash_2|
33
+ diffs = HashDiff.best_diff(hash_1, hash_2).map do |diff|
34
+ diff[1].match(/(\w+)\.?/)[1]
35
+ end
36
+ diff_keys.merge diffs
37
+ end
38
+ end
39
+ keys = keys - diff_keys
40
+ key_hash = keys.inject({}) do |hash, key|
41
+ hash[key] = @hashes[0][key]
42
+ hash
43
+ end
44
+ [key_hash, diff_keys]
45
+ end
46
+
47
+ def create_romxl_class(keys, parent)
48
+ @subclass_names << parent
49
+ name = "#{parent.to_s}#{@subclass_names.count(parent)}"
50
+ clazz = new_roxml_class name, @runtime.fetch(parent) do
51
+ include RoxmlSubclassHelpers
52
+ end
53
+ clazz.defaults = keys
54
+ @runtime.add_class name, clazz
55
+ @subclasses << clazz
56
+ name
57
+ end
58
+
59
+ def convert_roxml(obj, keys, class_name)
60
+ parent = @runtime.fetch(class_name)
61
+ new = parent.new
62
+ parent.plain_accessors.each do |new_accessor|
63
+ old_accessor = new_accessor.to_s.gsub(VAR_SUFIX, '').to_sym
64
+ new.send "#{new_accessor}=", obj.send(old_accessor)
65
+ end
66
+
67
+ keys.each do |accessor|
68
+ new.send "#{accessor}=", obj.send(accessor)
69
+ end
70
+ new
71
+ end
72
+
73
+ def convert_roxml_objs
74
+ fields, diff_keys = similar_fields
75
+ parent_name = @roxml_objs[0].class.class_name
76
+ name = create_romxl_class fields, parent_name
77
+ @roxml_objs.map do |roxml|
78
+ convert_roxml roxml, diff_keys, name
79
+ end
80
+ end
81
+ end
@@ -4,14 +4,18 @@ module PlainAccessors
4
4
  end
5
5
 
6
6
  module ClassMethods
7
- def plain_accessor(name)
8
- @plain_accessors ||= []
9
- @plain_accessors << name
10
- attr_accessor name
7
+ def plain_accessor(name, default = nil)
8
+ @plain_accessors ||= {}
9
+ @plain_accessors[name] = default
10
+ attr_writer name
11
+ define_method(name) do
12
+ instance_variable_get "@#{name}" || @plain_accessors[name]
13
+ end
11
14
  end
12
15
 
13
- def plain_accessors
14
- @plain_accessors || []
16
+ def plain_accessors(hash = false)
17
+ return @plain_accessors || {} if hash
18
+ (@plain_accessors && @plain_accessors.keys) || []
15
19
  end
16
20
  end
17
21
  end
@@ -1,9 +1,10 @@
1
1
  # require 'roxml'
2
2
  require 'nokogiri'
3
- require 'roundtrip_xml/plain_accessors'
3
+ require 'roundtrip_xml/utils'
4
4
  # Builds dynamic classes based on an XML file.
5
5
  # Classes that already exist in the DslRuntime instance are modified if necessary, not overridden.
6
6
  class RoxmlBuilder
7
+ include Utils
7
8
  def initialize (root, current_classes = {})
8
9
  # @type Nokogiri::Element
9
10
  @root = root
@@ -11,21 +12,15 @@ class RoxmlBuilder
11
12
  # @type Map<Symbol, ROXML>
12
13
  @generated_classes = current_classes
13
14
 
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
15
+ @root_class = @generated_classes[name_to_sym(root.name)] || new_roxml_class(@root.name)
23
16
 
24
17
  @generated_classes[ name_to_sym(@root.name)] = @root_class
25
18
  @nodes = {}
26
19
  end
20
+ def root_class_name
21
+ name_to_sym @root.name
22
+ end
27
23
  def build_classes
28
- # @root_class.xml_name (@root.name)
29
24
  @root.attributes.values.to_a.concat(@root.children.to_a).each do |child|
30
25
  default_opts = {from:child.name}
31
26
  if is_leaf_element?(child)
@@ -46,14 +41,6 @@ class RoxmlBuilder
46
41
  @generated_classes
47
42
  end
48
43
 
49
- def name_to_sym(name, lower_case = false)
50
- # name.gsub('-', '_').to_sym
51
- new_name = name.split('-').collect(&:capitalize).join
52
- new_name[0] = new_name[0].downcase! if lower_case
53
- new_name.to_sym
54
- end
55
-
56
-
57
44
  def add_accessor(name, opts = {}, node)
58
45
  attrs = @root_class.roxml_attrs
59
46
  attr = attrs.find do |a|
@@ -0,0 +1,11 @@
1
+ module RoxmlSubclassHelpers
2
+ def self.included(base)
3
+ base.extend ClassMethods
4
+ end
5
+ module ClassMethods
6
+ attr_writer :defaults
7
+ def defaults
8
+ @defaults || {}
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,108 @@
1
+ # require 'rubygems'
2
+ require 'ruby2ruby'
3
+ require 'ruby_parser'
4
+ require 'pp'
5
+ require 'sexp_processor'
6
+
7
+ class SexpDslBuilder
8
+ def exp
9
+ rub = <<EOF
10
+ bar 'adf'
11
+ foo do
12
+ a "a" do
13
+ a1 1
14
+ a2 2
15
+ end
16
+ a "a" do
17
+ a1 3
18
+ a2 4
19
+ end
20
+ b "b"
21
+ c "c"
22
+ end
23
+ EOF
24
+
25
+ # s = Sexp.from_array arr
26
+ # processor = Ruby2Ruby.new
27
+ # processor.process(s)
28
+ parser = RubyParser.new
29
+ s = parser.process(rub)
30
+ s
31
+ end
32
+
33
+ attr_accessor :roxml_objs, :subclasses, :runtime
34
+ def initialize(roxml_objs, subclasses, runtime)
35
+ self.roxml_objs = roxml_objs
36
+ self.subclasses = subclasses
37
+ self.runtime = runtime
38
+ end
39
+
40
+ def write_def_classes
41
+ arr = []
42
+ subclasses.each do |clazz|
43
+ defaults = clazz.defaults.map do |accessor, default|
44
+ [:call, nil, accessor, [:str, default]]
45
+ end
46
+ arr = [:iter,
47
+ [:call, nil, :define,
48
+ [:lit, clazz.class_name],
49
+ [:lit, clazz.superclass.class_name]
50
+ ], 0, [:block, *defaults]]
51
+ end
52
+
53
+ sexp = Sexp.from_array arr
54
+ processor = Ruby2Ruby.new
55
+ processor.process(sexp)
56
+ end
57
+
58
+ def create_sexp_for_roxml_obj(obj, root_method = nil)
59
+ is_subclass = obj.class.respond_to?(:defaults)
60
+ subclass_value = obj.class.respond_to?(:defaults) ? [:lit, obj.class.class_name] : nil
61
+ accessors = []
62
+ obj.attributes.each do |attr|
63
+ val = obj.send attr.accessor
64
+ if !val || (is_subclass && obj.class.defaults.keys.include?(attr.accessor))
65
+ next
66
+ end
67
+ if attr.sought_type.class == Symbol
68
+ accessors << [:call, nil, attr.accessor, [:str, val || '']]
69
+ elsif val.class == Array
70
+ val.each { |v| accessors << create_sexp_for_roxml_obj(v, attr.accessor) }
71
+ else
72
+ accessors << create_sexp_for_roxml_obj(val, attr.accessor) if val
73
+ end
74
+ end.compact
75
+ root_call = [:call, nil, root_method]
76
+ root_call << subclass_value if subclass_value
77
+ if root_method
78
+ [:iter, root_call,
79
+ 0,
80
+ [:block, *accessors]
81
+ ]
82
+ else
83
+ [:block,
84
+ *accessors]
85
+ end
86
+
87
+
88
+ end
89
+
90
+ def write_roxml_obj(obj, root_method)
91
+ s = create_sexp_for_roxml_obj obj, root_method
92
+
93
+ sexp = Sexp.from_array s
94
+ processor = Ruby2Ruby.new
95
+ processor.process(sexp).gsub "\"", "'"
96
+ end
97
+
98
+ def write_roxml_objs(root_method = nil)
99
+ roxml_objs.inject('') do |out, obj|
100
+ out += write_roxml_obj obj, root_method
101
+ out += "\n\n"
102
+ out
103
+ end
104
+
105
+ end
106
+
107
+
108
+ end
@@ -0,0 +1,53 @@
1
+ require 'roxml'
2
+ require 'roundtrip_xml/plain_accessors'
3
+ require 'set'
4
+ def name_to_sym_helper(name, lower_case = false)
5
+ new_name = name.split('-').collect(&:capitalize).join
6
+ new_name[0] = new_name[0].downcase! if lower_case
7
+ new_name.to_sym
8
+ end
9
+ module Utils
10
+
11
+ VAR_SUFIX = '_v'.freeze
12
+ def self.included(base)
13
+ unless base.const_defined?(:VAR_SUFIX)
14
+ base.const_set :VAR_SUFIX, Utils::VAR_SUFIX
15
+ end
16
+ end
17
+ def new_roxml_class(name, parent = Object, &block)
18
+ Class.new(parent) do
19
+ include ROXML
20
+ include PlainAccessors
21
+ xml_convention :dasherize
22
+ xml_name name
23
+ def attributes
24
+ self.class.roxml_attrs
25
+ end
26
+
27
+ def self.class_name
28
+ name_to_sym_helper self.tag_name
29
+ end
30
+
31
+ def to_hash
32
+ attributes.inject({}) do |hash, a|
33
+ value = a.to_ref(self).to_xml(self)
34
+ value = value.to_hash if value.respond_to? :to_hash
35
+
36
+ hash[a.accessor] = value
37
+ hash
38
+ end
39
+ end
40
+ def self.unique_parent_accessors
41
+ plain = Set.new(plain_accessors.map {|accessor| accessor.to_s.gsub(VAR_SUFIX, '').to_sym})
42
+ parent_accessors = Set.new(roxml_attrs.map { |attr| attr.accessor })
43
+ parent_accessors - plain
44
+ end
45
+ class_eval &block if block_given?
46
+ end
47
+
48
+ end
49
+
50
+ def name_to_sym(name, lower_case = false)
51
+ name_to_sym_helper(name, lower_case)
52
+ end
53
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roundtrip_xml
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Usick
@@ -77,10 +77,14 @@ files:
77
77
  - lib/roundtrip_xml/base_cleanroom.rb
78
78
  - lib/roundtrip_xml/dsl_builder.rb
79
79
  - lib/roundtrip_xml/dsl_runtime.rb
80
+ - lib/roundtrip_xml/extractor.rb
80
81
  - lib/roundtrip_xml/plain_accessors.rb
81
82
  - lib/roundtrip_xml/root_cleanroom.rb
82
83
  - lib/roundtrip_xml/roxml_builder.rb
83
- homepage: https://github.com/chrisUsick/roundtrip-xml
84
+ - lib/roundtrip_xml/roxml_subclass_helpers.rb
85
+ - lib/roundtrip_xml/sexp_dsl_builder.rb
86
+ - lib/roundtrip_xml/utils.rb
87
+ homepage: https://github.com/chrisUsick/roundtrip_xml
84
88
  licenses:
85
89
  - MIT
86
90
  metadata: {}