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 +106 -4
- data/VERSION +1 -1
- data/examples/amazon.rb +5 -5
- data/examples/phrk.rb +173 -0
- data/examples/posts.rb +0 -2
- data/examples/twitter.rb +3 -3
- data/examples/xml/phrk.xml +3 -3
- data/lib/oxmlk/attr.rb +34 -2
- data/lib/oxmlk.rb +350 -8
- data/oxmlk.gemspec +4 -2
- metadata +4 -2
data/README.rdoc
CHANGED
@@ -1,6 +1,108 @@
|
|
1
|
-
=
|
1
|
+
=OxMlk (Object XML Kit or something like that)
|
2
2
|
|
3
|
-
OxMlk is a
|
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
|
-
|
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.
|
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
|
13
|
+
class ItemSearchResponse
|
14
14
|
include OxMlk
|
15
15
|
|
16
|
-
ox_tag
|
16
|
+
ox_tag :camelcase
|
17
17
|
|
18
|
-
ox_elem :total_results, :
|
19
|
-
ox_elem :total_pages, :
|
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 =
|
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
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 :
|
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 :
|
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
|
39
|
+
ox_tag :statuses
|
40
40
|
|
41
41
|
ox_elem :statuses, :as => [Status]
|
42
42
|
end
|
data/examples/xml/phrk.xml
CHANGED
@@ -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
|
9
|
-
<Say voice="man" loop="4" language="en">
|
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
|
-
|
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
|
-
|
33
|
-
|
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
|
-
|
39
|
-
|
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.
|
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-
|
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.
|
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-
|
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
|