smg 0.0.2 → 0.1.0

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.rdoc CHANGED
@@ -8,6 +8,7 @@ Backed by Nokogiri's SAX Parser.
8
8
  * Typecasting
9
9
  * Nested resources (aka has_one)
10
10
  * Collections (aka has_many)
11
+ * Contextual parsing
11
12
 
12
13
  == EXAMPLES:
13
14
 
@@ -38,7 +39,7 @@ Backed by Nokogiri's SAX Parser.
38
39
 
39
40
  end
40
41
 
41
- === discogs.com
42
+ === discogs.com (with context)
42
43
 
43
44
  class Label
44
45
 
@@ -49,10 +50,9 @@ Backed by Nokogiri's SAX Parser.
49
50
  extract :release , :at => :id, :as => :discogs_id, :class => :integer
50
51
  extract :release , :at => :status
51
52
 
52
- root 'release'
53
- extract :title
54
- extract :catno
55
- extract :artist
53
+ extract 'release/title'
54
+ extract 'release/catno'
55
+ extract 'release/artist'
56
56
 
57
57
  end
58
58
 
@@ -61,10 +61,18 @@ Backed by Nokogiri's SAX Parser.
61
61
  root 'resp/label'
62
62
  extract :name
63
63
  extract :profile
64
- collect 'releases/release' , :as => :releases, :class => Release
64
+ collect 'releases/release' , :as => :releases, :class => Release, :context => [:with_releases]
65
65
 
66
66
  end
67
67
 
68
+ label = Label.parse(xml)
69
+ label.name #=> "name of the label"
70
+ label.releases #=> []
71
+
72
+ label = Label.parse(xml, :with_releases)
73
+ label.name #=> "name of the label"
74
+ label.releases #=> [#<Label::Release...>, ... #<Label::Release...>]
75
+
68
76
  == REQUIREMENTS:
69
77
 
70
78
  nokogiri (>=1.3)
@@ -0,0 +1,39 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
2
+
3
+ class Label
4
+
5
+ class Release
6
+
7
+ include SMG::Resource
8
+
9
+ extract 'release' , :at => :id, :as => :discogs_id, :class => :integer
10
+ extract 'release' , :at => :status
11
+
12
+ extract 'release/title'
13
+ extract 'release/catno'
14
+ extract 'release/artist'
15
+
16
+ end
17
+
18
+ include SMG::Resource
19
+
20
+ root 'resp/label'
21
+ extract 'name'
22
+ extract 'profile'
23
+ collect 'releases/release' , :as => :releases, :class => Release, :context => [:with_releases]
24
+
25
+ end
26
+
27
+ data = File.read(ROOT.join('spec/fixtures/discogs/Genosha+Recordings.xml'))
28
+
29
+ label = Label.parse(data)
30
+ puts label.name
31
+ puts label.profile
32
+ puts label.releases.map { |release| "#{release.catno}: #{release.title}" }.join("\n")
33
+
34
+ label = Label.parse(data,:with_releases)
35
+ puts label.name
36
+ puts label.profile
37
+ puts label.releases.map { |release| "#{release.catno}: #{release.title}" }.join("\n")
38
+
39
+ # EOF
@@ -0,0 +1,10 @@
1
+ require 'pathname'
2
+
3
+ ROOT = Pathname(__FILE__).dirname.expand_path.parent
4
+
5
+ dir = ROOT.join('lib').to_s
6
+ $:.unshift(dir) unless $:.include?(dir)
7
+
8
+ require 'smg'
9
+
10
+ # EOF
data/examples/plant.rb ADDED
@@ -0,0 +1,89 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
2
+
3
+ class Conservation
4
+
5
+ include SMG::Resource
6
+
7
+ extract :conservation , :at => :code
8
+ extract :conservation , :at => :year , :context => [:conservation]
9
+ extract :conservation , :as => :status , :context => [:conservation]
10
+
11
+ end
12
+
13
+ class Plant
14
+
15
+ include SMG::Resource
16
+
17
+ root 'spec'
18
+
19
+ extract 'family' , :context => [:classification]
20
+ extract 'genus' , :context => [:classification]
21
+ extract 'binomial'
22
+ extract 'conservation' , :context => [:conservation, :info], :class => Conservation
23
+ collect 'synonims/binomial' , :context => [:synonims], :as => :synonims,
24
+
25
+ end
26
+
27
+ data = <<-XML
28
+ <?xml version="1.0" encoding="UTF-8"?>
29
+ <spec>
30
+ <family>Rosaceae</family>
31
+ <genus>Malus</genus>
32
+ <conservation code="NE" year="2007">Not Evaluated</conservation>
33
+ <binomial>Malus pumila Mill.</binomial>
34
+ <synonims>
35
+ <binomial>Malus communis Poir.</binomial>
36
+ <binomial>Malus domestica auct. non Borkh.</binomial>
37
+ <binomial>Malus praecox (Pall.) Borkh.</binomial>
38
+ <binomial>Malus pumila Mill. var. niedzwetzkyana (Dieck) C.K. Schneid.</binomial>
39
+ <binomial>Malus sylvestris Amer. auth., non (L.) Mill.</binomial>
40
+ <binomial>Malus sylvestris (L.) Mill. var. praecox (Pall.) Ponomar.</binomial>
41
+ <binomial>Pyrus pumila (Mill.) K. Koch</binomial>
42
+ </synonims>
43
+ </spec>
44
+ XML
45
+
46
+ plant = Plant.parse(data,:classification)
47
+
48
+ puts plant.family #=> "Rosaceae"
49
+ puts plant.genus #=> "Malus"
50
+ puts plant.binomial #=> "Malus pumila Mill."
51
+ puts plant.conservation #=> nil
52
+ puts plant.synonims #=> []
53
+
54
+ plant = Plant.parse(data,:synonims)
55
+
56
+ puts plant.family #=> nil
57
+ puts plant.genus #=> nil
58
+ puts plant.binomial #=> "Malus pumila Mill."
59
+ puts plant.conservation #=> nil
60
+ puts plant.synonims #=> [ "Malus communis Poir.",
61
+ # "Malus domestica auct. non Borkh.",
62
+ # "Malus praecox (Pall.) Borkh.",
63
+ # "Malus pumila Mill. var. niedzwetzkyana (Dieck) C.K. Schneid.",
64
+ # "Malus sylvestris Amer. auth., non (L.) Mill.",
65
+ # "Malus sylvestris (L.) Mill. var. praecox (Pall.) Ponomar.",
66
+ # "Pyrus pumila (Mill.) K. Koch" ]
67
+
68
+ plant = Plant.parse(data,:conservation)
69
+
70
+ puts plant.family #=> nil
71
+ puts plant.genus #=> nil
72
+ puts plant.binomial #=> "Malus pumila Mill."
73
+ puts plant.conservation.status #=> "Not Evaluated"
74
+ puts plant.conservation.code #=> "NE"
75
+ puts plant.conservation.year #=> "2007"
76
+ puts plant.synonims #=> []
77
+
78
+ plant = Plant.parse(data,:info)
79
+
80
+ puts plant.family #=> nil
81
+ puts plant.genus #=> nil
82
+ puts plant.binomial #=> "Malus pumila Mill."
83
+ puts plant.conservation.status #=> nil
84
+ puts plant.conservation.code #=> "NE"
85
+ puts plant.conservation.status #=> nil
86
+ puts plant.synonims #=> []
87
+
88
+
89
+ # EOF
@@ -0,0 +1,35 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
2
+
3
+ class Status
4
+ include SMG::Resource
5
+
6
+ root 'status'
7
+
8
+ extract :id , :class => :integer, :as => :status_id
9
+ extract :created_at , :class => :datetime
10
+ extract :text
11
+
12
+ end
13
+
14
+ class User
15
+ include SMG::Resource
16
+
17
+ root 'user'
18
+
19
+ extract :id , :class => :integer, :as => :twitter_id
20
+ extract :location , :class => :string
21
+ extract :status , :class => Status
22
+ extract :created_at , :class => :datetime
23
+ extract :name
24
+ extract :screen_name
25
+
26
+ end
27
+
28
+ data = File.read(ROOT.join('spec/fixtures/twitter/pipopolam.xml'))
29
+ user = User.parse(data)
30
+
31
+ puts "#{user.screen_name} (#{user.name}), since #{user.created_at.strftime('%Y.%m.%d')}"
32
+ puts user.location
33
+ puts user.status.text
34
+
35
+ # EOF
data/lib/smg/document.rb CHANGED
@@ -3,12 +3,13 @@ module SMG #:nodoc:
3
3
 
4
4
  attr_reader :object, :thing
5
5
 
6
- def initialize(object, thing = nil)
6
+ def initialize(object, context = nil, thing = nil)
7
7
  @object = object
8
8
  @mapping = object.class.mapping
9
9
  @stack = []
10
10
  @docs = []
11
11
  @thing = thing
12
+ @context = context
12
13
  @chars = ""
13
14
  end
14
15
 
@@ -18,26 +19,33 @@ module SMG #:nodoc:
18
19
 
19
20
  if doc = @docs.last
20
21
  doc.start_element(name, attrs)
21
- elsif thing = @mapping.nested[@stack]
22
- @docs << doc = Document.new(thing.data_class.new,thing)
22
+ elsif (thing = @mapping.nested[@stack]) && thing.in_context_of?(@context)
23
+ @docs << doc = Document.new(thing.data_class.new,@context,thing)
23
24
  doc.start_element(name, attrs)
24
25
  end
25
26
 
26
27
  if !attrs.empty? && maps = @mapping.attributes[@stack]
27
- maps.values_at(*Hash[*attrs].keys).compact.each do |m|
28
- ix = attrs.index(m.at)
29
- @object.__send__(m.accessor, m.cast(attrs.at(ix+=1))) if ix
28
+ attrh = Hash[*attrs]
29
+ maps.values_at(*attrh.keys).compact.each do |m|
30
+ @object.__send__(m.accessor, m.cast(attrh[m.at])) if m.in_context_of?(@context)
30
31
  end
31
32
  end
32
33
 
33
- @element = @mapping.elements[@stack]
34
- @chars = ""
34
+ if (e = @mapping.elements[@stack]) && e.in_context_of?(@context)
35
+ @element = e
36
+ @chars = ""
37
+ end
35
38
 
36
39
  end
37
40
 
38
41
  def end_element(name)
39
- @object.send(@element.accessor, @element.cast(@chars)) if @element && @chars
40
- @chars, @element = nil, nil
42
+
43
+ if @element
44
+ @object.__send__(@element.accessor, @element.cast(@chars))
45
+ @chars = ""
46
+ @element = nil
47
+ end
48
+
41
49
  if doc = @docs.last
42
50
  doc.end_element(name)
43
51
  if doc.thing.path == @stack
@@ -45,13 +53,14 @@ module SMG #:nodoc:
45
53
  @docs.pop
46
54
  end
47
55
  end
56
+
48
57
  @stack.pop
58
+
49
59
  end
50
60
 
51
61
  def characters(string)
52
62
  if doc = @docs.last
53
63
  doc.characters(string)
54
- @chars ||= ""
55
64
  @chars << string
56
65
  elsif @element
57
66
  @chars << string
data/lib/smg/mapping.rb CHANGED
@@ -12,22 +12,25 @@ module SMG #:nodoc:
12
12
 
13
13
  def attach_element(tag,options)
14
14
  chain = handle_path(tag)
15
+ thing = Element.new(chain, options)
15
16
  if options.key?(:at)
16
- thing = Element.new(chain, options)
17
17
  @attributes[chain] ||= {}
18
18
  @attributes[chain][thing.at] = thing
19
19
  else
20
- @elements[chain] = Element.new(chain, options)
20
+ @elements[chain] = thing
21
21
  end
22
+ thing
22
23
  end
23
24
 
24
25
  def attach_nested(tag,options)
25
26
  chain = handle_path(tag)
26
- @nested[chain] = Element.new(chain, options.merge(:nested => true))
27
+ thing = Element.new(chain, options.merge(:nested => true))
28
+ @nested[chain] = thing
29
+ thing
27
30
  end
28
31
 
29
32
  def use_root(path)
30
- @root = normalize_path(path) # just a root tag for further definitions
33
+ @root = normalize_path(path)
31
34
  end
32
35
 
33
36
  private
@@ -2,26 +2,36 @@ module SMG #:nodoc:
2
2
  class Mapping #:nodoc:
3
3
 
4
4
  class Element
5
- attr_reader :path, :name, :accessor, :data_class, :cast_to, :at
5
+ attr_reader :path, :name, :accessor, :data_class, :cast_to, :at, :context
6
6
 
7
7
  def initialize(path, options = {})
8
8
 
9
9
  @name = (options[:as] || options[:at] || path.last).to_sym
10
10
  @path = path
11
11
  @collection = !!options[:collection]
12
- @accessor = @collection ? :"attach_#{@name}" : :"#{@name}="
12
+ @accessor = @collection ? :"append_to_#{@name}" : :"#{@name}="
13
13
  @data_class = nil
14
14
  @cast_to = nil
15
+ @context = nil
15
16
 
16
- if c = options[:class]
17
- if Class === c
18
- raise ArgumentError, "#{c} is not an SMG::Model" unless c.include?(::SMG::Resource)
19
- @data_class = c
20
- elsif Symbol === c
21
- raise ArgumentError, "#{c} is not a valid typecast" unless TypeCasts.key?(c)
22
- @cast_to = c
17
+ if options.key?(:context)
18
+ if Array === options[:context]
19
+ @context = options[:context].compact
20
+ @context.uniq!
21
+ @context = nil if @context.empty?
23
22
  else
24
- raise ArgumentError, ":class should be an SMG::Model or a Symbol"
23
+ raise ArgumentError, ":context should be an Array"
24
+ end
25
+ end
26
+
27
+ if options.key?(:class)
28
+ klass = options[:class]
29
+ if SMG::Model === klass
30
+ @data_class = klass
31
+ elsif TypeCasts.key?(klass)
32
+ @cast_to = klass
33
+ else
34
+ raise ArgumentError, ":class should be an SMG::Model or a valid typecast"
25
35
  end
26
36
  end
27
37
 
@@ -40,6 +50,10 @@ module SMG #:nodoc:
40
50
  @collection
41
51
  end
42
52
 
53
+ def in_context_of?(context)
54
+ @context.nil? || @context.include?(context)
55
+ end
56
+
43
57
  end
44
58
 
45
59
  end
data/lib/smg/model.rb CHANGED
@@ -43,8 +43,12 @@ module SMG #:nodoc:
43
43
  mapping.use_root(tag)
44
44
  end
45
45
 
46
- def parse(xml)
47
- self.new.parse(xml)
46
+ def parse(data, context = nil)
47
+ resource = new
48
+ doc = SMG::Document.new(resource,context)
49
+ ::Nokogiri::XML::SAX::Parser.new(doc).parse(data)
50
+ resource.instance_variable_set(:@_parsed, true)
51
+ resource
48
52
  end
49
53
 
50
54
  def mapping
data/lib/smg/resource.rb CHANGED
@@ -5,10 +5,8 @@ module SMG #:nodoc:
5
5
  base.extend Model
6
6
  end
7
7
 
8
- def parse(data)
9
- doc = SMG::Document.new(self)
10
- ::Nokogiri::XML::SAX::Parser.new(doc).parse(data)
11
- self
8
+ def parsed?
9
+ @_parsed
12
10
  end
13
11
 
14
12
  end
data/lib/smg/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module SMG
2
- VERSION = '0.0.2'
2
+ VERSION = '0.1.0'
3
3
  end
4
4
 
5
5
  # EOF
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
+ - 1
7
8
  - 0
8
- - 2
9
- version: 0.0.2
9
+ version: 0.1.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - SSDany
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-04-17 00:00:00 +04:00
17
+ date: 2010-04-29 00:00:00 +04:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -31,7 +31,8 @@ dependencies:
31
31
  type: :runtime
32
32
  version_requirements: *id001
33
33
  description: |
34
- Object to xml mapping library with simple declarative syntax.
34
+ XML to Object mapping library with simple declarative syntax.
35
+ Supports 'contextual' parsing.
35
36
  Backed by Nokogiri's SAX Parser.
36
37
 
37
38
  email: inadsence@gmail.com
@@ -43,6 +44,10 @@ extra_rdoc_files:
43
44
  - README.rdoc
44
45
  files:
45
46
  - README.rdoc
47
+ - examples/helper.rb
48
+ - examples/discogs.rb
49
+ - examples/twitter.rb
50
+ - examples/plant.rb
46
51
  - lib/smg/document.rb
47
52
  - lib/smg/mapping/element.rb
48
53
  - lib/smg/mapping/typecasts.rb