roundtrip_xml 0.1.3 → 0.1.4

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.
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: {}