oxmlk 0.3.2 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -1,6 +1,108 @@
1
- = oxmlk
1
+ =OxMlk (Object XML Kit or something like that)
2
2
 
3
- OxMlk is a simple lightweight XML mapper. Its structure is inspired by happymapper and its syntax takes after ROXML. Right now there is one example to get things going. More features, examples and documentation will be coming soon.
3
+ OxMlk is a flexible, lightweight, Object to XML mapper. It gives you a dsl for describing the relationship between your object and an XML structure. Once defined you can use the from_xml to create objects from an XML file and the to_xml file to export you objects as XML. Full documentation is hosted at {http://rdoc.info/projects/hexorx/oxmlk rdoc.info}.
4
+
5
+ ==Why?
6
+
7
+ Aren't there already XML mappers out there? Yes! Some really good ones. {http://github.com/Empact/roxml ROXML} has a great syntax and wonderful documentation. But .... it wasn't able to do something that I really needed it to do. You see OxMlk is an extraction from a parser for PhrkML. PhrkML is used to control call flow on VoIP systems. It has a list of action tags that tell the call what to do. They have to happen in order and each one is its own object. I found some hacks I could do to make ROXML kinda do what I wanted but it was pretty ugly. So I looked for something else and found {http://github.com/jnunemaker/happymapper HappyMapper}. It had an impressively simple design but it also didn't do what I need. So I made OxMlk. It tries to combine the syntax of {http://github.com/Empact/roxml ROXML} and the simple design of {http://github.com/jnunemaker/happymapper HappyMapper} while adding some features to make it extra flexible.
8
+
9
+ ==Acknowledgements
10
+
11
+ The syntax and even the documentation borrow heavily from {http://github.com/Empact/roxml ROXML} and the design is inspired by {http://github.com/jnunemaker/happymapper HappyMapper}.
12
+
13
+ =Quick Start Guide
14
+
15
+ This is a short tutorial based on the ROXML Quick Start Guide. The full rdoc is hosted at {http://rdoc.info/projects/hexorx/oxmlk rdoc.info} and has more detail in OxMlk::Attr and OxMlk::Elem.
16
+
17
+ ==Basic Mapping
18
+
19
+ Consider an XML document representing a Library containing a number of Books. You can map this structure to Ruby classes that provide addition useful behavior. With OxMlk, you can annotate the Ruby classes as follows:
20
+
21
+ class Book
22
+ include OxMlk
23
+
24
+ ox_attr :isbn, :from => 'ISBN' # attribute with name 'ISBN'
25
+ ox_elem :title
26
+ ox_elem :description
27
+ ox_elem :author
28
+ end
29
+
30
+ class Library
31
+ include OxMlk
32
+
33
+ ox_elem :name, :from => 'NAME'
34
+ ox_elem :books, :as => [Book] # by default oxmlk will use Book.tag to determine what to search for.
35
+ end
36
+
37
+ To create a library and put a number of books in it we could run the following code:
38
+
39
+ book = Book.new
40
+ book.isbn = "0201710897"
41
+ book.title = "The PickAxe"
42
+ book.description = "Best Ruby book out there!"
43
+ book.author = "David Thomas, Andrew Hunt, Dave Thomas"
44
+
45
+ lib = Library.new
46
+ lib.name = "Favorite Books"
47
+ lib.books = [book]
48
+
49
+ To save this information to an XML file:
50
+
51
+ doc = ROXML::XML::Document.new
52
+ doc.root = lib.to_xml
53
+ doc.save("library.xml")
54
+
55
+ To later populate the library object from the XML file:
56
+
57
+ lib = Library.from_xml(File.read("library.xml"))
58
+
59
+ Similarly, to do a one-to-one mapping between XML objects, such as book and publisher,
60
+ you would add a reference to another ROXML class. For example:
61
+
62
+ <book isbn="0974514055">
63
+ <title>Programming Ruby - 2nd Edition</title>
64
+ <description>Second edition of the great book.</description>
65
+ <publisher>
66
+ <name>Pragmatic Bookshelf</name>
67
+ </publisher>
68
+ </book>
69
+
70
+ can be mapped using the following code:
71
+
72
+ class Publisher
73
+ include OxMlk
74
+
75
+ ox_tag :downcase
76
+ xml_elem :name
77
+
78
+ # other important functionality
79
+ end
80
+
81
+ class BookWithPublisher
82
+ include OxMlk
83
+
84
+ ox_tag 'book'
85
+ ox_elem :publisher, :as => Publisher
86
+
87
+ # or, alternatively, if no class is needed to hang functionality on:
88
+ # ox_elem :publisher, :from => 'name', :in => 'publisher'
89
+ end
90
+
91
+ Note: In the above example, _ox_tag_ annotation tells OxMlk to set the element name for the BookWithPublisher class to 'book' for mapping to XML. The default XML element name is the class name, so 'BookWithPublisher' in this case. In the Publisher class it is set to the symbol :downcase, this tells OxMlk to take the class name and attempt to convert it to all lower case. That means OxMlk will look for <publisher> instead of <Publisher> This behavior will be inherited by the attrs and elems in the class as well. So (ox_elem :Name) would look for a <name> instead of <Name>.
92
+
93
+ == Manipulation
94
+
95
+ Extending the above examples, say you want to parse a book's page count and have it available as an Integer. In such a case, you can extend any object with a block to manipulate it's value at parse time. For example:
96
+
97
+ class Dog
98
+ include OxMlk
99
+
100
+ ox_attr(:age, :from => :human_years, :as => Integer) {|years| years * 7 }
101
+ end
102
+
103
+ The result of the block above is stored, rather than the actual value parsed from the document. It is important to keep in mind that manipulating data in this manner is one way. So when to_xml is ran you will get an age attribute not a human_years attribute.
104
+
105
+ = Extra Stuff
4
106
 
5
107
  == Note on Patches/Pull Requests
6
108
 
@@ -10,9 +112,9 @@ OxMlk is a simple lightweight XML mapper. Its structure is inspired by happymapp
10
112
  future version unintentionally.
11
113
  * Commit, do not mess with rakefile, version, or history.
12
114
  (if you want to have your own version, that is fine but
13
- bump version in a commit by itself I can ignore when I pull)
115
+ bump version in a commit by itself I can ignore when I pull)
14
116
  * Send me a pull request. Bonus points for topic branches.
15
117
 
16
118
  == Copyright
17
119
 
18
- Copyright (c) 2009 Josh Robinson. See LICENSE for details.
120
+ Copyright (c) 2009 Josh Robinson. See LICENSE for details.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.2
1
+ 0.3.3
data/examples/amazon.rb CHANGED
@@ -10,17 +10,17 @@ class Item
10
10
  ox_elem :point, :from => 'georss:point'
11
11
  end
12
12
 
13
- class Response
13
+ class ItemSearchResponse
14
14
  include OxMlk
15
15
 
16
- ox_tag 'ItemSearchResponse'
16
+ ox_tag :camelcase
17
17
 
18
- ox_elem :total_results, :from => 'TotalResults', :as => Integer, :in => 'Items'
19
- ox_elem :total_pages, :from => 'TotalPages', :as => Integer, :in => 'Items'
18
+ ox_elem :total_results, :as => Integer, :in => 'Items'
19
+ ox_elem :total_pages, :as => Integer, :in => 'Items'
20
20
  ox_elem :items, :as => [Item], :in => 'Items'
21
21
  end
22
22
 
23
- response = Response.from_xml(xml_for(:amazon))
23
+ response = ItemSearchResponse.from_xml(xml_for(:amazon))
24
24
 
25
25
  puts response.total_results, response.total_pages
26
26
 
data/examples/phrk.rb ADDED
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env ruby
2
+ require File.expand_path(File.dirname(__FILE__) + '/../spec/spec_helper')
3
+
4
+ ## This example is pretty ugly. It should be pretty when moved to the phrk gem.
5
+
6
+ class Say
7
+ include OxMlk
8
+
9
+ ox_elem :phrase, :from => :content
10
+
11
+ def description
12
+ "Say '#{phrase}'"
13
+ end
14
+ end
15
+
16
+ class Play
17
+ include OxMlk
18
+
19
+ ox_elem :file, :from => :content
20
+
21
+ def description
22
+ "Play #{file}"
23
+ end
24
+ end
25
+
26
+ class Gather
27
+ include OxMlk
28
+
29
+ ox_attr :action
30
+ ox_attr :method
31
+ ox_attr :timeout, :as => Integer
32
+ ox_attr :finish_on_key, :from => 'finishOnKey'
33
+ ox_attr :num_digits, :from => 'numDigits', :as => Integer
34
+
35
+ ox_elem :verbs, :as => [Say,Play]
36
+
37
+ def description
38
+ "Gather #{num_digits} keypresses ending on key '#{finish_on_key}' or timing out at #{timeout} seconds, then send #{method} to #{action}:\n\t#{verbs.map(&:description).join("\n\t")}"
39
+ end
40
+ end
41
+
42
+ class Record
43
+ include OxMlk
44
+
45
+ ox_attr :action
46
+ ox_attr :method
47
+ ox_attr :max_length, :from => 'maxLength', :as => Integer
48
+ ox_attr :finish_on_key, :from => 'finishOnKey'
49
+
50
+ def description
51
+ "Record for #{max_length} seconds max ending on keys '#{finish_on_key}' then send #{method} to #{action}"
52
+ end
53
+ end
54
+
55
+ class Redirect
56
+ include OxMlk
57
+
58
+ ox_elem :destination, :from => :content
59
+
60
+ def description
61
+ "Redirect to #{destination}"
62
+ end
63
+ end
64
+
65
+ class Pause
66
+ include OxMlk
67
+
68
+ ox_attr :length
69
+
70
+ def description
71
+ "Pause for #{length} seconds"
72
+ end
73
+ end
74
+
75
+ class Hangup
76
+ include OxMlk
77
+
78
+ def description
79
+ 'Hangup'
80
+ end
81
+ end
82
+
83
+ class Number
84
+ include OxMlk
85
+
86
+ ox_attr :send_digits, :from => 'sendDigits'
87
+ ox_elem :number, :from => :content
88
+
89
+ def description
90
+ "Call #{number} and send digits '#{send_digits}'"
91
+ end
92
+ end
93
+
94
+ class Dial
95
+ include OxMlk
96
+
97
+ ox_attr :callerid
98
+ ox_elem :numbers, :as => [Number]
99
+
100
+ def description
101
+ "Dial numbers with caller id #{callerid}:\n\t#{numbers.map(&:description).join("\n\t")}"
102
+ end
103
+ end
104
+
105
+ class Tag
106
+ include OxMlk
107
+
108
+ ox_elem :tag, :from => :content
109
+
110
+ def description
111
+ "Add tag '#{tag}'"
112
+ end
113
+ end
114
+
115
+ class Tags
116
+ include OxMlk
117
+
118
+ ox_attr :mode
119
+ ox_attr :include
120
+ ox_attr :exclude
121
+
122
+ def description
123
+ "Tagged with (#{include}) but not (#{exclude}) using match mode(#{mode})"
124
+ end
125
+ end
126
+
127
+ class Schedule
128
+ include OxMlk
129
+
130
+ ox_attr :mode
131
+ ox_attr :tz_offset
132
+ ox_attr :time
133
+ ox_attr :year
134
+ ox_attr :month
135
+ ox_attr :day_of_week
136
+ ox_attr :day_of_month
137
+
138
+ def description
139
+ "Schedule: mode(#{mode}) tz_offset(#{tz_offset}) time(#{time}) year(#{year}) month(#{month}) day_of_week(#{day_of_week} day_of_month(#{day_of_month}))"
140
+ end
141
+ end
142
+
143
+ VERBS = [Say,Play,Gather,Record,Redirect,Pause,Hangup,Dial,Tag]
144
+
145
+ class Rule
146
+ include OxMlk
147
+
148
+ ox_attr :name
149
+ ox_attr :mode
150
+ ox_elem :conditions, :as => [Schedule,Tags], :in => 'Conditions'
151
+ ox_elem :match_verbs, :as => VERBS, :in => 'Match'
152
+ ox_elem :no_match_verbs, :as => VERBS, :in => 'NoMatch'
153
+
154
+ def description
155
+ "Rule #{name} - match mode(#{mode})\n\tConditions:\n\t#{conditions.map(&:description).join("\n\t")}\n\n\tOn Match:\n\t#{match_verbs.map(&:description).join("\n\t")}\n\n\tNo Match:\n\t#{no_match_verbs.map(&:description).join("\n\t")}"
156
+ end
157
+ end
158
+
159
+ class Response
160
+ include OxMlk
161
+
162
+ ox_elem :verbs, :as => VERBS + [Rule]
163
+ end
164
+
165
+ class String
166
+ def description
167
+ self
168
+ end
169
+ end
170
+
171
+ response = Response.from_xml(xml_for(:phrk))
172
+
173
+ puts *response.verbs.map{|x| [x,'']}.flatten.map(&:description)
data/examples/posts.rb CHANGED
@@ -3,7 +3,6 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec/spec_helper')
3
3
 
4
4
  class Post
5
5
  include OxMlk
6
-
7
6
  ox_tag :post
8
7
 
9
8
  ox_attr :href
@@ -17,7 +16,6 @@ end
17
16
 
18
17
  class Response
19
18
  include OxMlk
20
-
21
19
  ox_tag :posts
22
20
 
23
21
  ox_attr :user
data/examples/twitter.rb CHANGED
@@ -4,7 +4,7 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec/spec_helper')
4
4
  class User
5
5
  include OxMlk
6
6
 
7
- ox_tag :user
7
+ ox_tag :downcase
8
8
 
9
9
  ox_elem :id, :as => Integer
10
10
  ox_elem :name
@@ -20,7 +20,7 @@ end
20
20
  class Status
21
21
  include OxMlk
22
22
 
23
- ox_tag :status
23
+ ox_tag :downcase
24
24
 
25
25
  ox_elem :id, :as => Integer
26
26
  ox_elem :text
@@ -36,7 +36,7 @@ end
36
36
  class Response
37
37
  include OxMlk
38
38
 
39
- ox_tag 'statuses'
39
+ ox_tag :statuses
40
40
 
41
41
  ox_elem :statuses, :as => [Status]
42
42
  end
@@ -5,11 +5,10 @@
5
5
  <Say>Robinson</Say>
6
6
  <Play>http://foo.com/cowbell.mp3</Play>
7
7
  </Gather>
8
- <Record action="http://foo.edu/handleRecording.php" method="GET" maxLength="20" finishOnKey="123*"/>
9
- <Say voice="man" loop="4" language="en">Josh</Say>
8
+ <Record action="http://foo.edu/handleRecording" method="GET" maxLength="20" finishOnKey="123*"/>
9
+ <Say voice="man" loop="4" language="en">AHHHHHH</Say>
10
10
  <Redirect>http://www.foo.com/nextInstructions</Redirect>
11
11
  <Pause length="10"/>
12
- <Hangup/>
13
12
  <Dial callerid="1234567">
14
13
  <Number sendDigits="ww3">344444</Number>
15
14
  </Dial>
@@ -29,4 +28,5 @@
29
28
  <Tag>closed</Tag>
30
29
  </NoMatch>
31
30
  </Rule>
31
+ <Hangup/>
32
32
  </Response>
data/lib/oxmlk/attr.rb CHANGED
@@ -1,5 +1,9 @@
1
1
  module OxMlk
2
2
  class Attr
3
+
4
+ attr_reader :accessor, :setter,:from, :as, :procs, :tag_proc, :tag
5
+
6
+ # Named Procs for use in :as option.
3
7
  PROCS = (Hash.new {|h,k| k.to_proc rescue nil}).merge(
4
8
  Integer => :to_i.to_proc,
5
9
  Float => :to_f.to_proc,
@@ -7,8 +11,28 @@ module OxMlk
7
11
  Symbol => :to_sym.to_proc,
8
12
  :bool => proc {|a| fetch_bool(a)})
9
13
 
10
- attr_reader :accessor, :setter,:from, :as, :procs, :tag_proc, :tag
11
-
14
+ # Creates a new instance of Attr to be used as a template for converting
15
+ # XML to objects and from objects to XML
16
+ # @param [Symbol,String] name Sets the accessor methods for this attr.
17
+ # It is also used to guess defaults for :from and :as.
18
+ # For example if it ends with '?' and :as isn't set
19
+ # it will treat it as a boolean.
20
+ # @param [Hash] o the options for the new attr definition
21
+ # @option o [Symbol,String] :from (tag_proc.call(name))
22
+ # Tells OxMlk what the name of the XML attribute is.
23
+ # It defaults to name processed by the tag_proc.
24
+ # @option o [Symbol,String,Proc,Array<Symbol,String,Proc>] :as
25
+ # Tells OxMlk how to translate the XML.
26
+ # The argument is coerced into a Proc and applied to the string found in the XML attribute.
27
+ # If an Array is passed each Proc is applied in order with the results
28
+ # of the first being passed to the second and so on. If it isn't set
29
+ # and name ends in '?' it processes it as if :bool was passed otherwise
30
+ # it treats it as a string with no processing.
31
+ # Includes the following named Procs: Integer, Float, String, Symbol and :bool.
32
+ # @option o [Proc] :tag_proc (proc {|x| x}) Proc used to guess :from.
33
+ # The Proc is applied to name and the results used to find the XML attribute
34
+ # if :from isn't set.
35
+ # @yield [String] Adds anothe Proc that is applied to the value.
12
36
  def initialize(name,o={},&block)
13
37
  name = name.to_s
14
38
  @accessor = name.chomp('?').intern
@@ -20,10 +44,18 @@ module OxMlk
20
44
  @procs = ([*as].map {|k| PROCS[k]} + [block]).compact
21
45
  end
22
46
 
47
+ private
48
+
49
+ # Finds @tag in data and applies procs.
23
50
  def from_xml(data)
24
51
  procs.inject(XML::Node.from(data)[tag]) {|d,p| p.call(d) rescue d}
25
52
  end
26
53
 
54
+ # Converts a value to a Boolean.
55
+ # @param [Symbol,String,Integer] value Value to convert
56
+ # @return [Boolean] Returns true if value is 'true', 'yes', 't' or 1.
57
+ # Returns false if value is 'false', 'no', 'f' or 0.
58
+ # If can't be convertet to a Boolean then the value is returned.
27
59
  def self.fetch_bool(value)
28
60
  value = value.to_s.downcase
29
61
  return true if %w{true yes 1 t}.include? value
data/lib/oxmlk.rb CHANGED
@@ -8,7 +8,11 @@ require File.join(dir, 'oxmlk/xml')
8
8
  require File.join(dir, 'oxmlk/attr')
9
9
  require File.join(dir, 'oxmlk/elem')
10
10
 
11
+ # Mixin to add annotation methods to you ruby classes.
12
+ # See {ClassMethods#ox_tag ox_tag}, {ClassMethods#ox_attr ox_attr} and {ClassMethods#ox_elem ox_elem} for available annotations.
11
13
  module OxMlk
14
+
15
+ private
12
16
 
13
17
  def self.included(base)
14
18
  base.class_eval do
@@ -21,26 +25,359 @@ module OxMlk
21
25
  end
22
26
 
23
27
  module InstanceMethods
28
+
29
+ # Returns a LibXML::XML::Node representing this object
24
30
  def to_xml
25
31
  self.class.to_xml(self)
26
32
  end
27
33
  end
28
34
 
35
+ # This class defines the annotation methods that are mixed into your Ruby classes for XML mapping information and behavior.
36
+ # See {ClassMethods#ox_tag ox_tag}, {ClassMethods#ox_attr ox_attr} and {ClassMethods#ox_elem ox_elem} for available annotations.
29
37
  module ClassMethods
30
38
  attr_accessor :ox_attrs, :ox_elems, :tag_proc
31
39
 
32
- def ox_attr(name,o={})
33
- new_attr = Attr.new(name, o.reverse_merge(:tag_proc => @tag_proc))
40
+ # Declares a reference to a certain xml attribute.
41
+ #
42
+ # == Sym Option
43
+ # [sym] Symbol representing the name of the accessor
44
+ #
45
+ # === Default naming
46
+ # This is what it will use to lookup the attribute if a name isn't defined in :from. For example:
47
+ #
48
+ # ox_attr :bob
49
+ # ox_attr :pony
50
+ #
51
+ # are equivalent to:
52
+ #
53
+ # ox_attr :bob, :from => 'bob'
54
+ # ox_attr :pony, :from => 'pony'
55
+ #
56
+ # === Boolean attributes
57
+ # If the name ends in a ?, OxMlk will attempt to coerce the value to true or false,
58
+ # with true, yes, t and 1 mapping to true and false, no, f and 0 mapping
59
+ # to false, as shown below:
60
+ #
61
+ # ox_elem :desirable?
62
+ # ox_attr :bizzare?, :from => 'BIZZARE'
63
+ #
64
+ # x = #from_xml(%{
65
+ # <object BIZZARE="1">
66
+ # <desirable>False</desirable>
67
+ # </object>
68
+ # })
69
+ #
70
+ # x.desirable?
71
+ # => false
72
+ # x.bizzare?
73
+ # => true
74
+ #
75
+ # When a block is provided the value will be passed to the block
76
+ # where unexpected values can be handled. If no block is provided
77
+ # the unexpected value will be returned.
78
+ #
79
+ # #from_xml(%{
80
+ # <object BIZZARE="Dunno"\>
81
+ # }).desirable?
82
+ # => "Dunno"
83
+ #
84
+ # ox_attr :bizzare? do |val|
85
+ # val.upcase
86
+ # end
87
+ #
88
+ # #from_xml(%{
89
+ # <object BIZZARE="Dunno"\>
90
+ # }).strange?
91
+ # => "DUNNO"
92
+ #
93
+ # == Blocks
94
+ # You may also pass a block which manipulates the associated parsed value.
95
+ #
96
+ # class Muffins
97
+ # include OxMlk
98
+ #
99
+ # ox_attr(:count, :from => 'bakers_dozens') {|val| val.to_i * 13 }
100
+ # end
101
+ #
102
+ # Blocks are always passed the value after being manipulated by the :as option
103
+ # and are the last thing to manipulate the XML. This is different than the
104
+ # ox_elem annotation that is passed an Array.
105
+ #
106
+ # == Options
107
+ # === :as
108
+ # ==== Basic Types
109
+ # Allows you to specify one of several basic types to return the value as. For example
110
+ #
111
+ # ox_elem :count, :as => Integer
112
+ #
113
+ # is equivalent to:
114
+ #
115
+ # ox_elem(:count) {|val| Integer(val) unless val.empty?}
116
+ #
117
+ # Such block shorthands for Integer, Float, String, Symbol and Time
118
+ # are currently available.
119
+ #
120
+ # If an Array is passed each Type in the array will be applied to the value in order.
121
+ #
122
+ # === :from
123
+ # The name by which the xml value will be found in XML. Default is sym.
124
+ def ox_attr(name,o={},&block)
125
+ new_attr = Attr.new(name, o.reverse_merge(:tag_proc => @tag_proc),&block)
34
126
  @ox_attrs << new_attr
35
127
  attr_accessor new_attr.accessor
36
128
  end
37
129
 
38
- def ox_elem(name,o={})
39
- new_elem = Elem.new(name, o.reverse_merge(:tag_proc => @tag_proc))
130
+ # Declares a reference to a certain xml element or a typed collection of elements.
131
+ #
132
+ # == Sym Option
133
+ # [sym] Symbol representing the name of the accessor.
134
+ #
135
+ # === Default naming
136
+ # This name will be the default node searched for, if no other is declared. For example:
137
+ #
138
+ # ox_elem :bob
139
+ # ox_elem :pony
140
+ #
141
+ # are equivalent to:
142
+ #
143
+ # ox_elem :bob, :from => 'bob'
144
+ # ox_elem :pony, :from => 'pony'
145
+ #
146
+ # === Boolean attributes
147
+ # If the name ends in a ?, OxMlk will attempt to coerce the value to true or false,
148
+ # with true, yes, t and 1 mapping to true and false, no, f and 0 mapping
149
+ # to false, as shown below:
150
+ #
151
+ # ox_elem :desirable?
152
+ # ox_attr :bizzare?, :from => 'BIZZARE'
153
+ #
154
+ # x = #from_xml(%{
155
+ # <object BIZZARE="1">
156
+ # <desirable>False</desirable>
157
+ # </object>
158
+ # })
159
+ #
160
+ # x.desirable?
161
+ # => false
162
+ # x.bizzare?
163
+ # => true
164
+ #
165
+ # When a block is provided the value will be passed to the block
166
+ # where unexpected values can be handled. If no block is provided
167
+ # the unexpected value will be returned.
168
+ #
169
+ # #from_xml(%{
170
+ # <object>
171
+ # <desirable>Dunno</desirable>
172
+ # </object>
173
+ # }).desirable?
174
+ # => "Dunno"
175
+ #
176
+ # ox_elem :strange? do |val|
177
+ # val.upcase
178
+ # end
179
+ #
180
+ # #from_xml(%{
181
+ # <object>
182
+ # <strange>Dunno</strange>
183
+ # </object>
184
+ # }).strange?
185
+ # => "DUNNO"
186
+ #
187
+ # == Blocks
188
+ # You may also pass a block which manipulates the associated parsed value.
189
+ #
190
+ # class Muffins
191
+ # include OxMlk
192
+ #
193
+ # ox_elem(:count, :from => 'bakers_dozens') {|val| val.first.to_i * 13 }
194
+ # end
195
+ #
196
+ # Blocks are always passed an Array and are the last thing to manipulate the XML.
197
+ # The array will include all elements already manipulated by anything passed to
198
+ # the :as option. If the :as option is an Array and no block is passed then the
199
+ # Array is returned unmodified. If the :as option is nil or is not an Array then
200
+ # only the first element is returned.
201
+ #
202
+ # == Options
203
+ # === :as
204
+ # ==== Basic Types
205
+ # Allows you to specify one of several basic types to return the value as. For example
206
+ #
207
+ # ox_elem :count, :as => Integer
208
+ #
209
+ # is equivalent to:
210
+ #
211
+ # ox_elem(:count) {|val| Integer(val.first) unless val.first.empty?}
212
+ #
213
+ # Such block shorthands for Integer, Float, String, Symbol and Time
214
+ # are currently available.
215
+ #
216
+ # To reference many elements, put the desired type in a literal array. e.g.:
217
+ #
218
+ # ox_elem :counts, :as => [Integer]
219
+ #
220
+ # Even an array of text nodes can be specified with :as => []
221
+ #
222
+ # ox_elem :quotes, :as => []
223
+ #
224
+ # === Other OxMlk Classes
225
+ # Declares an accessor that represents another OxMlk class as child XML element
226
+ # (one-to-one or composition) or array of child elements (one-to-many or
227
+ # aggregation) of this type. Default is one-to-one. For one-to-many, simply pass the class
228
+ # as the only element in an array. You can also put several OxMlk classes in an Array that
229
+ # will act like a polymorphic one-to-many association.
230
+ #
231
+ # Composition example:
232
+ # <book>
233
+ # <publisher>
234
+ # <name>Pragmatic Bookshelf</name>
235
+ # </publisher>
236
+ # </book>
237
+ #
238
+ # Can be mapped using the following code:
239
+ # class Book
240
+ # include OxMlk
241
+ # ox_elem :publisher, :as => Publisher
242
+ # end
243
+ #
244
+ # Aggregation example:
245
+ # <library>
246
+ # <books>
247
+ # <book/>
248
+ # <book/>
249
+ # </books>
250
+ # </library>
251
+ #
252
+ # Can be mapped using the following code:
253
+ # class Library
254
+ # include OxMlk
255
+ # ox_elem :books, :as => [Book], :in => 'books'
256
+ # end
257
+ #
258
+ # If you don't have the <books> tag to wrap around the list of <book> tags:
259
+ # <library>
260
+ # <name>Ruby books</name>
261
+ # <book/>
262
+ # <book/>
263
+ # </library>
264
+ #
265
+ # You can skip the wrapper argument:
266
+ # ox_elem :books, :as => [Book]
267
+ #
268
+ # ==== Hash
269
+ # Unlike ROXML, OxMlk doesn't do anything special for hashes. However OxMlk
270
+ # applies :as to each element and the block once to the Array of elements.
271
+ # This means OxMlk can support setting hashes but it is more of a manual process.
272
+ # I am looking for a easier method but for now here are a few examples:
273
+ #
274
+ # ===== Hash of element contents
275
+ # For xml such as this:
276
+ #
277
+ # <dictionary>
278
+ # <definition>
279
+ # <word/>
280
+ # <meaning/>
281
+ # </definition>
282
+ # <definition>
283
+ # <word/>
284
+ # <meaning/>
285
+ # </definition>
286
+ # </dictionary>
287
+ #
288
+ # This is actually one of the more complex ones. It uses the :raw keyword to pass the block an array of LibXML::XML::Nodes
289
+ # ox_elem(:definitions, :from => 'definition', :as => [:raw]) do |vals|
290
+ # Hash[*vals.map {}|val| [val.search('word').content,val.search('meaning').content]}.flatten]
291
+ # end
292
+ #
293
+ # ===== Hash of :content
294
+ # For xml such as this:
295
+ #
296
+ # <dictionary>
297
+ # <definition word="quaquaversally">adjective: (of a geological formation) sloping downward from the center in all directions.</definition>
298
+ # <definition word="tergiversate">To use evasions or ambiguities; equivocate.</definition>
299
+ # </dictionary>
300
+ #
301
+ # This one can also be accomplished with the :raw keyword and a fancy block.
302
+ # ox_elem(:definitions, :from => 'definition', :as => [:raw]) {|x| Hash[*x.map {|val| [val['word'],val.content] }.flatten]}
303
+ #
304
+ # ===== Hash of :name
305
+ # For xml such as this:
306
+ #
307
+ # <dictionary>
308
+ # <quaquaversally>adjective: (of a geological formation) sloping downward from the center in all directions.</quaquaversally>
309
+ # <tergiversate>To use evasions or ambiguities; equivocate.</tergiversate>
310
+ # </dictionary>
311
+ #
312
+ # This one requires some fancy xpath in :from to grab all the elements
313
+ # ox_elem(:definitions, :from => './*', :as => [:raw]) {|x| Hash[*x.map {|val| [val.name,val.content] }.flatten]}
314
+ #
315
+ # === :from
316
+ # The name by which the xml value will be found in XML. Default is sym.
317
+ #
318
+ # This value may also include XPath notation.
319
+ #
320
+ # ==== :from => :content
321
+ # When :from is set to :content, this refers to the content of the current node,
322
+ # rather than a sub-node. It is equivalent to :from => '.'
323
+ #
324
+ # Example:
325
+ # class Contributor
326
+ # ox_elem :name, :from => :content
327
+ # ox_attr :role
328
+ # end
329
+ #
330
+ # To map:
331
+ # <contributor role="editor">James Wick</contributor>
332
+ #
333
+ # === :in
334
+ # An optional name of a wrapping tag for this XML accessor.
335
+ # This can include other xpath values, which will be joined with :from with a '/'
336
+ def ox_elem(name,o={},&block)
337
+ new_elem = Elem.new(name, o.reverse_merge(:tag_proc => @tag_proc),&block)
40
338
  @ox_elems << new_elem
41
339
  attr_accessor new_elem.accessor
42
340
  end
43
341
 
342
+ # Sets the name of the XML element that represents this class. Use this
343
+ # to override the default camelcase class name.
344
+ #
345
+ # Example:
346
+ # class BookWithPublisher
347
+ # ox_tag 'book'
348
+ # end
349
+ #
350
+ # Without the ox_tag annotation, the XML mapped tag would have been 'BookWithPublisher'.
351
+ #
352
+ # Most xml documents have a consistent naming convention, for example, the node and
353
+ # and attribute names might appear in CamelCase. ox_tag enables you to adapt
354
+ # the oxmlk default names for this object to suit this convention. For example,
355
+ # if I had a document like so:
356
+ #
357
+ # <XmlDoc>
358
+ # <MyPreciousData />
359
+ # <MoreToSee />
360
+ # </XmlDoc>
361
+ #
362
+ # Then I could access it's contents by defining the following class:
363
+ #
364
+ # class XmlDoc
365
+ # include OxMlk
366
+ #
367
+ # ox_tag :camelcase
368
+ # ox_elem :my_precious_data
369
+ # ox_elem :more_to_see
370
+ # end
371
+ #
372
+ # You may supply a block or any #to_proc-able object as the argument,
373
+ # and it will be called against the default node and attribute names before searching
374
+ # the document. Here are some example declaration:
375
+ #
376
+ # ox_tag :upcase
377
+ # ox_tag &:camelcase
378
+ # ox_tag {|val| val.gsub('_', '').downcase }
379
+ #
380
+ # See ActiveSupport::CoreExtensions::String::Inflections for more prepackaged formats
44
381
  def ox_tag(tag=nil,&block)
45
382
  raise 'you can only set tag or a block, not both.' if tag && block
46
383
 
@@ -56,10 +393,16 @@ module OxMlk
56
393
  end
57
394
  end
58
395
 
396
+ # Used to tell if this is an OxMlk Object.
59
397
  def ox?
60
398
  true
61
399
  end
62
400
 
401
+ # Returns a new instance from XML
402
+ # @param [XML::Document,XML::Node,File,Pathname,URI,String] data
403
+ # The xml data used to create a new instance.
404
+ # @return New instance generated from xml data passed in.
405
+ # Attr and Elem definitions are used to translate the xml to new object.
63
406
  def from_xml(data)
64
407
  xml = XML::Node.from(data)
65
408
  raise 'invalid XML' unless xml.name == ox_tag
@@ -69,6 +412,9 @@ module OxMlk
69
412
  ox
70
413
  end
71
414
 
415
+ # Returns XML generated from an instance based on Attr & Elem definitions.
416
+ # @param [Object] data An instance used to populate XML
417
+ # @return [XML::Node] Generated XML::Node
72
418
  def to_xml(data)
73
419
  ox = XML::Node.new(ox_tag)
74
420
  ox_elems.each do |elem|
@@ -80,10 +426,6 @@ module OxMlk
80
426
  end
81
427
  ox
82
428
  end
83
-
84
- def xml_convention(converter=nil)
85
- @xml_convention = converter
86
- end
87
429
  end
88
430
 
89
431
  end
data/oxmlk.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{oxmlk}
8
- s.version = "0.3.2"
8
+ s.version = "0.3.3"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Josh Robinson"]
12
- s.date = %q{2009-09-03}
12
+ s.date = %q{2009-09-08}
13
13
  s.description = %q{OxMlk gives you a simple way to map you ruby objects to XML and then convert one to the other.}
14
14
  s.email = %q{hexorx@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -24,6 +24,7 @@ Gem::Specification.new do |s|
24
24
  "VERSION",
25
25
  "examples/amazon.rb",
26
26
  "examples/example.rb",
27
+ "examples/phrk.rb",
27
28
  "examples/posts.rb",
28
29
  "examples/twitter.rb",
29
30
  "examples/xml/amazon.xml",
@@ -57,6 +58,7 @@ Gem::Specification.new do |s|
57
58
  "spec/xml_spec.rb",
58
59
  "examples/amazon.rb",
59
60
  "examples/example.rb",
61
+ "examples/phrk.rb",
60
62
  "examples/posts.rb",
61
63
  "examples/twitter.rb"
62
64
  ]
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oxmlk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josh Robinson
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-09-03 00:00:00 -06:00
12
+ date: 2009-09-08 00:00:00 -06:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -59,6 +59,7 @@ files:
59
59
  - VERSION
60
60
  - examples/amazon.rb
61
61
  - examples/example.rb
62
+ - examples/phrk.rb
62
63
  - examples/posts.rb
63
64
  - examples/twitter.rb
64
65
  - examples/xml/amazon.xml
@@ -114,5 +115,6 @@ test_files:
114
115
  - spec/xml_spec.rb
115
116
  - examples/amazon.rb
116
117
  - examples/example.rb
118
+ - examples/phrk.rb
117
119
  - examples/posts.rb
118
120
  - examples/twitter.rb