xml_mapper 0.3.3 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.3
1
+ 0.4.0
data/lib/xml_mapper.rb CHANGED
@@ -6,24 +6,10 @@ class XmlMapper
6
6
  class << self
7
7
  attr_accessor :mapper
8
8
 
9
- def text(*args)
10
- mapper.add_mapping(:text, *args)
11
- end
12
-
13
- def integer(*args)
14
- mapper.add_mapping(:integer, *args)
15
- end
16
-
17
- def boolean(*args)
18
- mapper.add_mapping(:boolean, *args)
19
- end
20
-
21
- def exists(*args)
22
- mapper.add_mapping(:exists, *args)
23
- end
24
-
25
- def node(*args)
26
- mapper.add_mapping(:node, *args)
9
+ [:text, :integer, :boolean, :exists, :not_exists, :node_name, :inner_text, :node, :attribute].each do |method_name|
10
+ define_method(method_name) do |*args|
11
+ mapper.add_mapping(method_name, *args)
12
+ end
27
13
  end
28
14
 
29
15
  def within(xpath, &block)
@@ -63,7 +49,7 @@ class XmlMapper
63
49
 
64
50
  def capture_submapping(&block)
65
51
  saved_mapper = self.mapper
66
- self.mapper = XmlMapper.new
52
+ self.mapper = self.new
67
53
  self.instance_eval(&block)
68
54
  captured_mapper = self.mapper
69
55
  self.mapper = saved_mapper
@@ -94,7 +80,10 @@ class XmlMapper
94
80
  def add_mapping(type, *args)
95
81
  options = extract_options_from_args(args)
96
82
  if args.first.is_a?(Hash)
97
- args.first.map { |xpath, key| add_single_mapping(type, xpath, key, options) }
83
+ if after_map_method = args.first.delete(:after_map)
84
+ options.merge!(:after_map => after_map_method)
85
+ end
86
+ args.first.map { |xpath, key| add_single_mapping(type, xpath, key, options) }
98
87
  else
99
88
  args.map { |arg| add_single_mapping(type, arg, arg, options) }
100
89
  end
@@ -104,8 +93,14 @@ class XmlMapper
104
93
  self.after_map_block = block
105
94
  end
106
95
 
107
- def add_single_mapping(type, xpath, key, options = {})
108
- self.mappings << { :type => type, :xpath => add_with_to_xpath(xpath), :key => key, :options => options }
96
+ def add_single_mapping(type, xpath_or_attribute, key, options = {})
97
+ mappings = { :type => type, :key => key, :options => options }
98
+ xpath = type == :attribute ? nil : xpath_or_attribute
99
+ if type == :attribute
100
+ mappings[:attribute] = xpath_or_attribute.to_s
101
+ end
102
+ mappings[:xpath] = add_with_to_xpath(xpath)
103
+ self.mappings << mappings
109
104
  end
110
105
 
111
106
  def add_with_to_xpath(xpath)
@@ -126,6 +121,7 @@ class XmlMapper
126
121
  xml_or_doc.map { |doc| attributes_from_xml(doc) }
127
122
  else
128
123
  doc = xml_or_doc.is_a?(Nokogiri::XML::Node) ? xml_or_doc : Nokogiri::XML(xml_or_doc)
124
+ doc = doc.root if doc.respond_to?(:root)
129
125
  atts = self.mappings.inject(xml_path.nil? ? {} : { :xml_path => xml_path }) do |hash, mapping|
130
126
  hash.merge(mapping[:key] => value_from_doc_and_mapping(doc, mapping))
131
127
  end
@@ -137,28 +133,44 @@ class XmlMapper
137
133
  def value_from_doc_and_mapping(doc, mapping)
138
134
  if mapping[:type] == :many
139
135
  mapping[:options][:mapper].attributes_from_xml(doc.search(mapping[:xpath]).to_a)
140
- elsif mapping[:type] == :exists
141
- !doc.at("//#{mapping[:xpath]}").nil?
142
136
  else
143
- value = mapping[:type] == :node ? doc.at(mapping[:xpath]) : inner_text_for_xpath(doc, mapping[:xpath])
144
- apply_after_map_to_value(value, mapping)
137
+ node = mapping[:xpath].length == 0 ? doc : doc.xpath(mapping[:xpath]).first
138
+ if mapping[:type] == :exists
139
+ !node.nil?
140
+ elsif mapping[:type] == :not_exists
141
+ node.nil?
142
+ else
143
+ value =
144
+ case mapping[:type]
145
+ when :node_name
146
+ doc.nil? ? nil : doc.name
147
+ when :inner_text
148
+ doc.nil? ? nil : doc.inner_text
149
+ when :node
150
+ node
151
+ when :attribute
152
+ node.nil? ? nil : (node.respond_to?(:root) ? node.root : node)[mapping[:attribute]]
153
+ else
154
+ inner_text_for_node(node)
155
+ end
156
+ apply_after_map_to_value(value, mapping)
157
+ end
145
158
  end
146
159
  end
147
160
 
148
161
  def apply_after_map_to_value(value, mapping)
149
- after_map = TYPE_TO_AFTER_CODE[mapping[:type]]
150
- after_map ||= mapping[:options][:after_map]
151
- if value && after_map
152
- value = value.send(after_map) if value.respond_to?(after_map)
153
- value = self.send(after_map, value) if self.respond_to?(after_map)
162
+ after_mappings = [TYPE_TO_AFTER_CODE[mapping[:type]], mapping[:options][:after_map]].compact
163
+ if value
164
+ after_mappings.each do |after_map|
165
+ value = value.send(after_map) if value.respond_to?(after_map)
166
+ value = self.send(after_map, value) if self.respond_to?(after_map)
167
+ end
154
168
  end
155
169
  value
156
170
  end
157
171
 
158
- def inner_text_for_xpath(doc, xpath)
159
- if node = doc.at(xpath)
160
- node.inner_text
161
- end
172
+ def inner_text_for_node(node)
173
+ node.inner_text if node
162
174
  end
163
175
 
164
176
  MAPPINGS = {
@@ -166,7 +178,9 @@ class XmlMapper
166
178
  "false" => false,
167
179
  "yes" => true,
168
180
  "y" => true,
169
- "n" => false
181
+ "n" => false,
182
+ "1" => true,
183
+ "0" => false
170
184
  }
171
185
 
172
186
  def string_to_boolean(value)
data/spec/example_spec.rb CHANGED
@@ -11,7 +11,10 @@ describe "ExampleSpec" do
11
11
  {
12
12
  :title => "Black on Both Sides", :version_title => "Extended Edition", :released_in => 1999,
13
13
  :artist_name => "Mos Def", :artist_id => 1212, :country => "DE", :allows_streaming => true,
14
- :tracks_count => 2, :released_on => Date.new(1999, 10, 12)
14
+ :tracks_count => 2, :released_on => Date.new(1999, 10, 12), :contributions => [
15
+ { :role => "artist", :name => "Mos Def" },
16
+ { :role => "producer", :name => "DJ Premier" },
17
+ ]
15
18
  }.each do |key, value|
16
19
  it "extracts #{value.inspect} as #{key}" do
17
20
  @attributes[key].should == value
@@ -19,8 +22,8 @@ describe "ExampleSpec" do
19
22
  end
20
23
 
21
24
  [
22
- { :track_title => "Fear Not of Man", :track_number => 1, :disk_number => 1, :explicit_lyrics => true },
23
- { :track_title => "Hip Hop", :track_number => 2, :disk_number => 1, :explicit_lyrics => true },
25
+ { :track_title => "Fear Not of Man", :track_number => 1, :disk_number => 1, :explicit_lyrics => true, :isrc => "1234" },
26
+ { :track_title => "Hip Hop", :track_number => 2, :disk_number => 1, :explicit_lyrics => false, :isrc => "2345" },
24
27
  ].each_with_index do |hash, offset|
25
28
  hash.each do |key, value|
26
29
  it "extracts #{value.inspect} for #{key} for track with offset #{offset}" do
@@ -9,17 +9,27 @@
9
9
  <name>Mos Def</name>
10
10
  <id>1212</id>
11
11
  </artist>
12
+ <contributions>
13
+ <artist>Mos Def</artist>
14
+ <producer>DJ Premier</producer>
15
+ </contributions>
12
16
  <tracks>
13
- <track>
17
+ <track code="1234">
14
18
  <title>Fear Not of Man</title>
15
19
  <number>1</number>
16
20
  <disk>1</number>
17
21
  <explicit_lyrics />
22
+ <not_streamable_in>
23
+ <country>de</country>
24
+ </not_streamable_in>
18
25
  </track>
19
- <track>
26
+ <track code="2345">
20
27
  <title>Hip Hop</title>
21
28
  <number>2</number>
22
29
  <disk>1</number>
30
+ <not_streamable_in>
31
+ <country>at</country>
32
+ </not_streamable_in>
23
33
  </track>
24
34
  </tracks>
25
35
  </album>
data/spec/my_mapper.rb CHANGED
@@ -13,11 +13,18 @@ class MyMapper < XmlMapper
13
13
  integer :id => :artist_id
14
14
  end
15
15
 
16
+ many "contributions/*" => :contributions do # use the name of xml nodes for mappings
17
+ node_name :role # map name of xml node to :role
18
+ inner_text :name # map inner_text of xml node to :name
19
+ end
20
+
16
21
  many "tracks/track" => :tracks do # maps xpath "tracks/track" to array with key :tracks
22
+ attribute :code => :isrc # map xml attribute "code" to :isrc
17
23
  text :title => :track_title
18
24
  integer :number => :track_number
19
25
  integer :disk => :disk_number
20
- exists :explicit_lyrics # checks if a node with the xpath exists
26
+ exists :explicit_lyrics # checks if a node with the xpath exists
27
+ not_exists "not_streamable_in/country[text()='de']" => :allows_streaming
21
28
  end
22
29
 
23
30
  after_map do # is called after attributes are extracted, self references the extracted attributes
@@ -29,6 +29,20 @@ describe "XmlMapper" do
29
29
  ]
30
30
  end
31
31
 
32
+ it "sets attribute for attribute mapping when attribute is mapped to same name" do
33
+ @mapper.add_mapping(:attribute, :first_name)
34
+ @mapper.mappings.should == [
35
+ { :type => :attribute, :attribute => "first_name", :key => :first_name, :options => {}, :xpath => "" }
36
+ ]
37
+ end
38
+
39
+ it "sets attribute for attribute mapping when attribute is mapped to different name" do
40
+ @mapper.add_mapping(:attribute, :first_name => :firstname)
41
+ @mapper.mappings.should == [
42
+ { :type => :attribute, :attribute => "first_name", :key => :firstname, :options => {}, :xpath => "" }
43
+ ]
44
+ end
45
+
32
46
  it "adds mappings when mapping given as hash" do
33
47
  @mapper.add_mapping(:text, :name => :first_name)
34
48
  @mapper.mappings.should == [{ :type => :text, :xpath => "name", :key => :first_name, :options => {} }]
@@ -61,6 +75,7 @@ describe "XmlMapper" do
61
75
  describe "#exists" do
62
76
  it "returns true when node exists" do
63
77
  xml = %(<album><title>Black on Both Sides</title><rights><country>DE</country></rights></album>)
78
+ root = Nokogiri::XML(xml).root
64
79
  @mapper.add_mapping(:exists, "rights[country='DE']" => :allows_streaming)
65
80
  @mapper.attributes_from_xml(xml).should == { :allows_streaming => true }
66
81
  end
@@ -138,10 +153,29 @@ describe "XmlMapper" do
138
153
  }
139
154
  end
140
155
 
156
+ it "allows multiple after_code mappings" do
157
+ class << @mapper
158
+ def double(value)
159
+ value * 2
160
+ end
161
+ end
162
+ @mapper.add_mapping(:integer, :track_number, :after_map => :double)
163
+ @mapper.attributes_from_xml(@xml).should == {
164
+ :track_number => 14
165
+ }
166
+ end
167
+
168
+ it "allows defining a after_map mapping without ridicolous brackets" do
169
+ @mapper.add_mapping(:string, :track_number => :num, :after_map => :to_i)
170
+ @mapper.attributes_from_xml(@xml).should == {
171
+ :num => 7
172
+ }
173
+ end
174
+
141
175
  it "uses mapper method defined in xml_mapper when value does not respond to :after_map and given as hash" do
142
176
  class << @mapper
143
177
  def double(value)
144
- value.to_s * 2
178
+ value * 2
145
179
  end
146
180
  end
147
181
  # [{:type=>:text, :xpath=>"Graphic/ImgFormat", :key=>:image_format, :options=>{:after_map=>:double}}]
@@ -153,12 +187,27 @@ describe "XmlMapper" do
153
187
 
154
188
  it "takes a nokogiri node as argument" do
155
189
  @mapper.add_mapping(:text, :artist_name)
156
- @mapper.attributes_from_xml(Nokogiri::XML(@xml)).should == {
190
+ @mapper.attributes_from_xml(Nokogiri::XML(@xml).root).should == {
191
+ :artist_name => "Mos Def"
192
+ }
193
+ end
194
+
195
+ it "takes a nokogiri document as argument" do
196
+ @mapper.add_mapping(:text, :artist_name)
197
+ @mapper.attributes_from_xml(Nokogiri::XML(@xml).root).should == {
157
198
  :artist_name => "Mos Def"
158
199
  }
159
200
  end
160
201
 
161
202
  it "should also takes an array of nodes as argument" do
203
+ @mapper.add_mapping(:text, :artist_name)
204
+ @mapper.attributes_from_xml([Nokogiri::XML(@xml).root, Nokogiri::XML(@xml).root]).should == [
205
+ { :artist_name => "Mos Def" },
206
+ { :artist_name => "Mos Def" }
207
+ ]
208
+ end
209
+
210
+ it "also takes an array of nokogiri documents as argument" do
162
211
  @mapper.add_mapping(:text, :artist_name)
163
212
  @mapper.attributes_from_xml([Nokogiri::XML(@xml), Nokogiri::XML(@xml)]).should == [
164
213
  { :artist_name => "Mos Def" },
@@ -265,7 +314,7 @@ describe "XmlMapper" do
265
314
  describe "#string_to_boolean" do
266
315
  {
267
316
  "true" => true, "false" => false, "y" => true, "TRUE" => true, "" => nil, "YES" => true, "yes" => true,
268
- "n" => false
317
+ "n" => false, "1" => true, "0" => false
269
318
  }.each do |value, result|
270
319
  it "converts #{value.inspect} to #{result}" do
271
320
  @mapper.string_to_boolean(value).should == result
@@ -351,6 +400,31 @@ describe "XmlMapper" do
351
400
  }
352
401
  end
353
402
 
403
+ it "allows after map when used in submapper and method exists in mapper" do
404
+ xml = %(
405
+ <album>
406
+ <tracks>
407
+ <track>
408
+ <track_number>11</track_number>
409
+ </track>
410
+ <track>
411
+ <track_number>22</track_number>
412
+ </track>
413
+ </tracks>
414
+ </album>
415
+ )
416
+ @clazz.send(:define_method, "double") do |*args|
417
+ args.first * 2
418
+ end
419
+
420
+ @clazz.many "tracks/track" => :tracks do
421
+ integer :track_number => :num, :after_map => :double
422
+ end
423
+ @clazz.attributes_from_xml(xml).should == {
424
+ :tracks => [ { :num => 22 }, { :num => 44 }]
425
+ }
426
+ end
427
+
354
428
  it "accepts boolean as keyword" do
355
429
  @clazz.boolean(:allows_streaming)
356
430
  xml = %(<album><title>Test Title</title><allows_streaming>true</allows_streaming></album>)
@@ -363,6 +437,41 @@ describe "XmlMapper" do
363
437
  @clazz.attributes_from_xml(xml).should == { :allows_streaming => true }
364
438
  end
365
439
 
440
+ it "accepts not_exists as keyword" do
441
+ @clazz.not_exists("forbidden_countries/country[text()='DE']" => :allows_streaming)
442
+ xml = %(<album><title>Black on Both Sides</title><forbidden_countries><country>DE</country></rights></album>)
443
+ @clazz.attributes_from_xml(xml).should == { :allows_streaming => false }
444
+ end
445
+
446
+ it "does keep scope for not_exists" do
447
+ xml = %(
448
+ <album>
449
+ <tracks>
450
+ <track>
451
+ <title>Track 1</title>
452
+ <forbidden_countries>
453
+ <country>DE</country>
454
+ </forbidden_countries>
455
+ </track>
456
+ <track>
457
+ <title>Track 2</title>
458
+ <forbidden_countries>
459
+ <country>AT</country>
460
+ </forbidden_countries>
461
+ </track>
462
+ </tracks>
463
+ </album>
464
+ )
465
+ @clazz.many "tracks/track" => :tracks do
466
+ text :title
467
+ not_exists "forbidden_countries/country[text()='DE']" => :allows_streaming
468
+ end
469
+ @clazz.attributes_from_xml(xml)[:tracks].should == [
470
+ { :title => "Track 1", :allows_streaming => false },
471
+ { :title => "Track 2", :allows_streaming => true },
472
+ ]
473
+ end
474
+
366
475
  describe "#within" do
367
476
  it "adds the within xpath to all xpath mappings" do
368
477
  @clazz.within("artist") do
@@ -511,5 +620,75 @@ describe "XmlMapper" do
511
620
  end
512
621
  end
513
622
 
623
+
624
+ describe "using node_name, inner_text and attribute" do
625
+ it "extracts attributes for a many relationship when node_name and inner_text is used" do
626
+ xml = %(
627
+ <album>
628
+ <Contributors>
629
+ <Performer>Sexy sushi</Performer>
630
+ <Composer>Sexi Sushi</Composer>
631
+ <Author>Sexi Sushi</Author>
632
+ </Contributors>
633
+ </album>
634
+ )
635
+ @clazz.many "Contributors/*" => :contributions do
636
+ node_name :contribution_type
637
+ inner_text :name
638
+ end
639
+ @clazz.attributes_from_xml(xml).should == {
640
+ :contributions => [
641
+ { :contribution_type => "Performer", :name => "Sexy sushi" },
642
+ { :contribution_type => "Composer", :name => "Sexi Sushi" },
643
+ { :contribution_type => "Author", :name => "Sexi Sushi" },
644
+ ]
645
+ }
646
+ end
647
+
648
+ it "extracts the correct attributes when attribute keyword is used in 'many'-mapping" do
649
+ xml = %(
650
+ <album>
651
+ <tracks>
652
+ <track some_code="1234">
653
+ <title>Track 1</title>
654
+ </track>
655
+ </tracks>
656
+ </album>
657
+ )
658
+ @clazz.many "tracks/track" => :tracks do
659
+ attribute "some_code" => :isrc
660
+ text :title
661
+ end
662
+ @clazz.attributes_from_xml(xml).should == {
663
+ :tracks => [
664
+ { :isrc => "1234", :title => "Track 1" }
665
+ ]
666
+ }
667
+ end
668
+
669
+ it "extracts the correct attributes when attribute is used in within-mapping" do
670
+ xml = %(
671
+ <album>
672
+ <meta code="1234">
673
+ <title>Product Title</title>
674
+ </meta>
675
+ </></album>
676
+ )
677
+ @clazz.within "meta" do
678
+ attribute "code" => :upc
679
+ end
680
+ @clazz.attributes_from_xml(xml)[:upc].should == "1234"
681
+ end
682
+
683
+ it "extracts the correct attributes when attribute is used in root mapping" do
684
+ xml = %(
685
+ <album code="1234">
686
+ <title>Product Title</title>
687
+ </></album>
688
+ )
689
+ @clazz.attribute :code => :upc
690
+ @clazz.attributes_from_xml(xml)[:upc].should == "1234"
691
+ end
692
+ end
514
693
  end
515
694
  end
data/xml_mapper.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{xml_mapper}
8
- s.version = "0.3.3"
8
+ s.version = "0.4.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Tobias Schwab"]
12
- s.date = %q{2010-11-29}
12
+ s.date = %q{2010-12-01}
13
13
  s.description = %q{Declarative XML to Ruby mapping}
14
14
  s.email = %q{tobias.schwab@dynport.de}
15
15
  s.extra_rdoc_files = [
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xml_mapper
3
3
  version: !ruby/object:Gem::Version
4
- hash: 21
4
+ hash: 15
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 3
9
- - 3
10
- version: 0.3.3
8
+ - 4
9
+ - 0
10
+ version: 0.4.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Tobias Schwab
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-11-29 00:00:00 +01:00
18
+ date: 2010-12-01 00:00:00 +01:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency