roxml 2.3.2 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,278 @@
1
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
2
+ <html>
3
+ <head>
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+ <link rel="stylesheet" type="text/css" media="screen" href="style.css">
12
+
13
+
14
+
15
+
16
+
17
+
18
+ <title>ROXML - Ruby Object to XML Mapping Library</title>
19
+ </head>
20
+
21
+
22
+ <body>
23
+
24
+
25
+
26
+
27
+ <div id="header" class="contentBlock">
28
+ <h1>ROXML - Ruby Object to XML Mapping Library</h1>
29
+
30
+
31
+
32
+
33
+ </div>
34
+
35
+
36
+
37
+
38
+ <div class="quickLinks"> <a href="#download">Download</a>
39
+ <a href="#quickstart">Quick Start Guide</a> <a href="http://rubyforge.org/projects/roxml">RubyForge Project
40
+ Site</a>
41
+ </div>
42
+
43
+
44
+
45
+
46
+ <div class="contentBlock">
47
+ <p>ROXML is a <a href="http://ruby-lang.org/" title="Main Ruby Website">Ruby</a>&nbsp;library
48
+ designed to make it easier for Ruby developers to work with <a href="http://en.wikipedia.org/wiki/XML">XML</a>.
49
+ Using
50
+ simple annotations, it enables Ruby classes to be custom-mapped to XML. ROXML
51
+ takes care of the marshalling and unmarshalling of mapped attributes so
52
+ that developers can focus on building first-class Ruby classes. As a
53
+ result, ROXML <span class="highlight">simplifies the
54
+ development of <a href="http://en.wikipedia.org/wiki/Representational_State_Transfer">RESTful</a>&nbsp;applications,
55
+ <a href="http://en.wikipedia.org/wiki/Web_Services">Web
56
+ Services</a>, and <a href="http://en.wikipedia.org/wiki/XML-RPC">XML-RPC</a>.</span></p>
57
+
58
+
59
+
60
+
61
+ <p>ROXML leverages the <a href="http://www.germane-software.com/software/rexml/">REXML</a>
62
+ Ruby XML processor. ROXML powers the <a href="http://rubyforge.org/projects/uddi4r">uddi4r -
63
+ UDDI&nbsp;for Ruby</a> project.</p>
64
+
65
+
66
+
67
+
68
+ <h2>ROXML 1.0 Features</h2>
69
+
70
+
71
+
72
+
73
+ ROXML 1.0 provides the following capabilities:<br>
74
+
75
+
76
+
77
+
78
+ <ul>
79
+
80
+
81
+
82
+
83
+ <li>Read Ruby objects from XML (marshal)</li>
84
+
85
+
86
+
87
+
88
+ <li>Write Ruby objects to XML (unmarshal)</li>
89
+
90
+
91
+
92
+
93
+ <li>Smart defaults for XML mapping</li>
94
+
95
+
96
+
97
+
98
+ <li>Annotation-style methods (also known as macros) for XML
99
+ mapping</li>
100
+
101
+
102
+
103
+
104
+ <li>One-to-one (composition) Ruby to XML</li>
105
+
106
+
107
+
108
+
109
+ <li>One-to-many (aggregation) Ruby with array to XML</li>
110
+
111
+
112
+
113
+
114
+ <li>UTF-8 support for multi-lingual documents</li>
115
+
116
+
117
+
118
+
119
+ </ul>
120
+
121
+
122
+ <h2>ROXML 1.1 Features</h2>
123
+
124
+
125
+
126
+
127
+ ROXML 1.1&nbsp;adds the following new capabilities:
128
+ <ul>
129
+ <li>Handling text elements with attributes</li>
130
+ <li>Support for mapped Ruby objects in modules</li>
131
+ </ul>
132
+ <p><a name="download"></a> </p>
133
+
134
+
135
+
136
+
137
+ <h2>Download</h2>
138
+
139
+
140
+
141
+
142
+ <p>You can download source and library releases on the <a href="http://rubyforge.org/frs/?group_id=305">RubyForge
143
+ downloads</a> page. Alternatively, you can checkout the latest
144
+ source&nbsp;from <a href="http://rubyforge.org/scm/?group_id=305">CVS</a>.</p>
145
+
146
+
147
+
148
+
149
+ <p> <a name="quickstart"></a> </p>
150
+
151
+
152
+
153
+
154
+ <h2>Quick Start Guide</h2>
155
+
156
+
157
+
158
+
159
+ <p>
160
+ This is a short usage example. See <a href="../doc/classes/ROXML/ROXML_Class.html">ROXML::ROXML_Class</a> and
161
+ packaged test cases for more information.</p>
162
+
163
+
164
+
165
+
166
+
167
+ <p>
168
+ Consider an XML document representing a Library containing a number of
169
+ Books. You can map this structure to Ruby classes that provide addition
170
+ useful behavior. With ROXML, you can
171
+ annotate the Ruby classes as follows:</p>
172
+
173
+
174
+
175
+
176
+ <pre><code><span style="font-weight: bold;">class</span> Book<br> <span style="font-weight: bold;">include</span> ROXML<br><br> xml_attribute :isbn, "ISBN"<br> xml_text :title<br> xml_text :description, <span style="font-weight: bold;">nil</span>, ROXML::TAG_CDATA<br> xml_text :author<span style="font-weight: bold;"></span><br><span style="font-weight: bold;">end</span><br><br><span style="font-weight: bold;">class</span> Library<br> <span style="font-weight: bold;">include</span> ROXML<br><br> xml_text :name, "NAME", ROXML::TAG_CDATA<br> xml_object :books, Book, ROXML::TAG_ARRAY, "books"<br><span style="font-weight: bold;">end</span><br></code><br></pre>
177
+
178
+
179
+
180
+
181
+ <p>To create a library and put a number of books in it, we could
182
+ run the following code:</p>
183
+
184
+
185
+ <pre> book = Book.<span style="font-weight: bold;">new</span>()<br> book.isbn = "0201710897"<br> book.title = "The PickAxe"<br> book.description = "Best Ruby book out there!"<br> book.author = "David Thomas, Andrew Hunt, Dave Thomas"<br><br> lib = Library.<span style="font-weight: bold;">new</span>()<br> lib.name = "Favorite Books"<br> lib.books &lt;&lt; book</pre>
186
+
187
+
188
+ <p>To save this information to an XML file:</p>
189
+
190
+
191
+
192
+
193
+ <pre><code>File.open("library.xml", "w") <span style="font-weight: bold;">do</span> |f|<br> lib.to_xml.write(f, 0)<br><span style="font-weight: bold;">end</span><br></code><br></pre>
194
+
195
+
196
+
197
+
198
+ <p>This would put the following XML into <em>library.xml</em>:</p>
199
+
200
+
201
+
202
+
203
+ <pre><code>&lt;library&gt;<br> &lt;NAME&gt;&lt;![CDATA[Favorite Books]]&gt;&lt;/NAME&gt;<br> &lt;books&gt;<br> &lt;book ISBN='0201710897'&gt;<br> &lt;title&gt;The PickAxe&lt;/title&gt;<br> &lt;description&gt;&lt;![CDATA[Best Ruby book out there!]]&gt;&lt;/description&gt;<br> &lt;author&gt;David Thomas, Andrew Hunt, Dave Thomas&lt;/author&gt;<br> &lt;/book&gt;<br> &lt;/books&gt;<br>&lt;/library&gt;</code><br></pre>
204
+
205
+
206
+
207
+
208
+ <p>To later populate the library object from the XML file:</p>
209
+
210
+
211
+
212
+
213
+ <pre> <code>lib = Library.parse(File.read("library.xml"))<br></code><br></pre>
214
+
215
+
216
+
217
+
218
+ <p>To do a one-to-one mapping between XML objects, such as book
219
+ and publisher,
220
+ you would use the <em>xml_object</em> annotation. For
221
+ example:</p>
222
+
223
+
224
+
225
+
226
+ <pre><code>&lt;book isbn="0974514055"&gt;<br> &lt;title&gt;Programming Ruby - 2nd Edition&lt;/title&gt;<br> &lt;description&gt;Second edition of the great book&lt;/description&gt;<br> &lt;publisher&gt;<br> &lt;name&gt;Pragmatic Bookshelf&lt;/name&gt;<br> &lt;/publisher&gt;<br>&lt;/book&gt;<br></code><br></pre>
227
+
228
+
229
+
230
+
231
+ <p>Can be mapped using the following code:</p>
232
+
233
+
234
+
235
+
236
+ <pre><code><span style="font-weight: bold;">class</span> BookWithPublisher<br> <span style="font-weight: bold;">include</span> ROXML<br><br> xml_name :book<br> xml_object :publisher, Publisher<br><span style="font-weight: bold;">end</span><br></code><br></pre>
237
+
238
+
239
+
240
+
241
+ <p>Note: In the above example, <em>xml_name</em>
242
+ annotation tells ROXML to set the element
243
+ name to "book" for mapping to XML. The default is XML element name is
244
+ the class name in lowercase; "bookwithpublisher"
245
+ in this case.</p>
246
+
247
+
248
+
249
+
250
+ </div>
251
+
252
+
253
+
254
+
255
+ <div id="footer" class="contentBlock">
256
+ <p>ROXML&nbsp;is licensed under the MIT License. See
257
+ bundled MIT-LICENSE.txt for details.&nbsp;ROXML was created by <a href="http://rubyforge.org/users/andersengstrom">Anders
258
+ Engstrom</a> and is being developed and maintained by <a href="http://rubyforge.org/users/zakmandhro">Zak Mandhro</a>.&nbsp;For
259
+ project tracking, downloads, and other information, visit
260
+ the <a href="http://rubyforge.org/projects/roxml">ROXML
261
+ RubyForge Project</a> page.</p>
262
+
263
+
264
+
265
+
266
+ </div>
267
+
268
+
269
+
270
+
271
+ <div id="cvs_id">$Id: INDEX,v 1.6 2006/06/28 03:52:38
272
+ zakmandhro Exp $</div>
273
+
274
+
275
+
276
+
277
+ </body>
278
+ </html>
@@ -0,0 +1,79 @@
1
+ /* Generated by CaScadeS, a stylesheet editor for Mozilla Composer */
2
+
3
+ body { margin: 0px;
4
+ font-size: 13px;
5
+ font-family: arial,sans-serif;
6
+ background: #DDD;
7
+ }
8
+
9
+ .highlight {
10
+ background: #FFD;
11
+ }
12
+ .contentBlock {
13
+ margin-left: 50px;
14
+ margin-top: 2px;
15
+ background: #FEFEFE;
16
+ width: 700px; padding: 10px;
17
+ border-left: solid 2px #FFF;
18
+ border-right: solid 2px #AAA;
19
+ }
20
+
21
+ .quickLinks { text-align: center;
22
+ margin-left: 50px;
23
+ padding-left: 10px; padding-right: 10px;
24
+ padding-top: 3px; padding-bottom: 3px;
25
+ margin-top: 2px;
26
+ border-left: solid 2px #FFF;
27
+ border-right: solid 2px #AAA;
28
+ background: white;
29
+ width: 700px;
30
+ }
31
+
32
+ .quickLinks a { padding-left: 5px;
33
+ padding-right: 5px;
34
+ text-decoration: underline;
35
+ }
36
+
37
+ .quickLinks a:hover {
38
+ background: #EEE;
39
+ }
40
+
41
+ a { text-decoration: none;
42
+ }
43
+
44
+ a:hover { }
45
+
46
+ code { font-size: 12px;
47
+ font-family: monospace;
48
+ }
49
+
50
+ pre { border: 1px solid rgb(221, 221, 221);
51
+ padding: 1em;
52
+ overflow: auto;
53
+ width: 70%;
54
+ background-color: rgb(238, 238, 238);
55
+ color: rgb(0, 0, 0);
56
+ }
57
+
58
+ h1 {
59
+ text-align: center;
60
+ font-size: 150%;
61
+ }
62
+
63
+ h2 { border-bottom: 1px solid rgb(221, 221, 221);
64
+ font-size: 150%;
65
+ }
66
+
67
+ h3 { font-size: 120%;
68
+ }
69
+
70
+ #footer { border-top: 1px dashed gray;
71
+ background-color: rgb(240, 240, 240);
72
+ font-size: x-small;
73
+ }
74
+
75
+ div#cvs_id { text-align: right;
76
+ color: rgb(204, 204, 204);
77
+ font-size: 75%;
78
+ }
79
+
@@ -3,14 +3,16 @@
3
3
  end
4
4
 
5
5
  module ROXML # :nodoc:
6
+ VERSION = '2.4.0'
7
+
6
8
  def self.included(base) # :nodoc:
7
- base.extend ClassMethods::Accessors
8
- base.extend ClassMethods::Declarations
9
- base.extend ClassMethods::Operations
9
+ base.extend ClassMethods::Accessors,
10
+ ClassMethods::Declarations,
11
+ ClassMethods::Operations
10
12
  base.class_eval do
11
- include InstanceMethods::Accessors
12
- include InstanceMethods::Construction
13
- include InstanceMethods::Conversions
13
+ include InstanceMethods::Accessors,
14
+ InstanceMethods::Construction,
15
+ InstanceMethods::Conversions
14
16
  end
15
17
  end
16
18
 
@@ -24,8 +26,9 @@ module ROXML # :nodoc:
24
26
 
25
27
  # Provides access to ROXML::ClassMethods::Accessors::tag_refs directly from an instance of a ROXML class
26
28
  def tag_refs
27
- self.class.tag_refs
29
+ self.class.tag_refs_without_deprecation
28
30
  end
31
+ deprecate :tag_refs => :roxml_attrs
29
32
  end
30
33
 
31
34
  module Construction
@@ -70,8 +73,9 @@ module ROXML # :nodoc:
70
73
  # Returns a LibXML::XML::Node or a REXML::Element representing this object
71
74
  def to_xml(name = nil)
72
75
  returning XML::Node.new_element(name || tag_name) do |root|
73
- tag_refs.each do |ref|
74
- v = __send__(ref.accessor)
76
+ self.class.roxml_attrs.each do |attr|
77
+ ref = attr.to_ref(self)
78
+ v = ref.to_xml
75
79
  unless v.nil?
76
80
  ref.update_xml(root, v)
77
81
  end
@@ -89,11 +93,6 @@ module ROXML # :nodoc:
89
93
  #
90
94
  module ClassMethods # :nodoc:
91
95
  module Declarations
92
- # A helper which enables us to detect when the xml_name has been explicitly set
93
- def xml_name? #:nodoc:
94
- @xml_name
95
- end
96
-
97
96
  # Sets the name of the XML element that represents this class. Use this
98
97
  # to override the default lowercase class name.
99
98
  #
@@ -105,8 +104,49 @@ module ROXML # :nodoc:
105
104
  # Without the xml_name annotation, the XML mapped tag would have been "bookwithpublisher".
106
105
  #
107
106
  def xml_name(name)
108
- @xml_name = true
109
- @tag_name = name
107
+ @roxml_tag_name = name
108
+ end
109
+
110
+ # Most xml documents have a consistent naming convention, for example, the node and
111
+ # and attribute names might appear in CamelCase. xml_convention enables you to adapt
112
+ # the roxml default names for this object to suit this convention. For example,
113
+ # if I had a document like so:
114
+ #
115
+ # <XmlDoc>
116
+ # <MyPreciousData />
117
+ # <MoreToSee InAttrs="" />
118
+ # </XmlDoc>
119
+ #
120
+ # Then I could access it's contents by defining the following class:
121
+ #
122
+ # class XmlDoc
123
+ # include ROXML
124
+ # xml_convention :camelcase
125
+ # xml_reader :my_precious_data
126
+ # xml_reader :in_attrs, :in => 'MoreToSee'
127
+ # end
128
+ #
129
+ # You may supply a block or any #to_proc-able object as the argument,
130
+ # and it will be called against the default node and attribute names before searching
131
+ # the document. Here are some example declaration:
132
+ #
133
+ # xml_convention :upcase
134
+ # xml_convention &:camelcase
135
+ # xml_convention {|val| val.gsub('_', '').downcase }
136
+ #
137
+ # See ActiveSupport::CoreExtensions::String::Inflections for more prepackaged formats
138
+ #
139
+ # Note that the xml_convention is also applied to the default root-level tag_name,
140
+ # but in this case an underscored version of the name is applied, for convenience.
141
+ def xml_convention(to_proc_able = nil, &block)
142
+ raise ArgumentError, "conventions are already set" if @roxml_naming_convention
143
+ raise ArgumentError, "only one conventions can be set" if to_proc_able.respond_to?(:to_proc) && block_given?
144
+ @roxml_naming_convention = to_proc_able.try(:to_proc)
145
+ @roxml_naming_convention = block if block_given?
146
+ end
147
+
148
+ def roxml_naming_convention # :nodoc:
149
+ (@roxml_naming_convention || superclass.try(:roxml_naming_convention)).freeze
110
150
  end
111
151
 
112
152
  # Declares an accesser to a certain xml element, whether an attribute, a node,
@@ -120,13 +160,13 @@ module ROXML # :nodoc:
120
160
  # This name will be the default node or attribute name searched for,
121
161
  # if no other is declared. For example,
122
162
  #
123
- # xml_reader :bob, :from => 'bob'
124
- # xml_accessor :pony, :attr => 'pony'
163
+ # xml_reader :bob
164
+ # xml_accessor :pony, :attr
125
165
  #
126
166
  # are equivalent to:
127
167
  #
128
- # xml_reader :bob
129
- # xml_accessor :pony, :attr
168
+ # xml_reader :bob, :from => 'bob'
169
+ # xml_accessor :pony, :attr => 'pony'
130
170
  #
131
171
  # === Boolean attributes
132
172
  # If the name ends in a ?, ROXML will attempt to coerce the value to true or false,
@@ -373,35 +413,35 @@ module ROXML # :nodoc:
373
413
  # [:in] An optional name of a wrapping tag for this XML accessor
374
414
  # [:else] Default value for attribute, if missing
375
415
  # [:required] If true, throws RequiredElementMissing when the element isn't present
416
+ # [:frozen] If true, all results are frozen (using #freeze) at parse-time.
376
417
  #
377
418
  def xml(sym, writable = false, type_and_or_opts = :text, opts = nil, &block)
378
419
  opts = Opts.new(sym, *[type_and_or_opts, opts].compact, &block)
379
420
 
380
- ref = case opts.type
381
- when :attr then XMLAttributeRef
382
- when :content then XMLTextRef
383
- when :text then XMLTextRef
384
- when :hash then XMLHashRef
385
- when Symbol then raise ArgumentError, "Invalid type argument #{opts.type}"
386
- else XMLObjectRef
387
- end.new(opts)
388
-
389
- add_accessor(ref, writable)
421
+ add_accessor(opts, writable)
390
422
  end
391
423
 
392
424
  # Declares a read-only xml reference. See xml for details.
425
+ #
426
+ # Note that while xml_reader does not create a setter for this attribute,
427
+ # its value can be modified indirectly via methods. For more complete
428
+ # protection, consider the :frozen option.
393
429
  def xml_reader(sym, type_and_or_opts = :text, opts = nil, &block)
394
430
  xml sym, false, type_and_or_opts, opts, &block
395
431
  end
396
432
 
397
433
  # Declares a writable xml reference. See xml for details.
434
+ #
435
+ # Note that while xml_accessor does create a setter for this attribute,
436
+ # you can use the :frozen option to prevent its value from being
437
+ # modified indirectly via methods.
398
438
  def xml_accessor(sym, type_and_or_opts = :text, opts = nil, &block)
399
439
  xml sym, true, type_and_or_opts, opts, &block
400
440
  end
401
441
 
402
442
  # This method is deprecated, please use xml_initialize instead
403
443
  def xml_construct(*args)
404
- present_tags = tag_refs.map(&:accessor)
444
+ present_tags = tag_refs_without_deprecation.map(&:accessor)
405
445
  missing_tags = args - present_tags
406
446
  unless missing_tags.empty?
407
447
  raise ArgumentError, "All construction tags must be declared first using xml, " +
@@ -413,25 +453,23 @@ module ROXML # :nodoc:
413
453
  deprecate :xml_construct => :xml_initialize
414
454
 
415
455
  private
416
- def add_accessor(ref, writable)
417
- if tag_refs.map(&:accessor).include? ref.accessor
418
- raise "Accessor #{ref.accessor} is already defined as XML accessor in class #{self.name}"
456
+ def add_accessor(attr, writable)
457
+ if roxml_attrs.map(&:accessor).include? attr.accessor
458
+ raise "Accessor #{attr.accessor} is already defined as XML accessor in class #{self.name}"
419
459
  end
420
- tag_refs << ref
460
+ @roxml_attrs << attr
421
461
 
422
- define_method(ref.accessor) do
423
- result = instance_variable_get("@#{ref.variable_name}")
462
+ define_method(attr.accessor) do
463
+ result = instance_variable_get("@#{attr.variable_name}")
424
464
  if result.nil?
425
- result = ref.default
426
- instance_variable_set("@#{ref.variable_name}", result)
465
+ result = attr.default
466
+ instance_variable_set("@#{attr.variable_name}", result)
427
467
  end
428
468
  result
429
469
  end
430
470
 
431
- if writable && !instance_methods.include?("#{ref.accessor}=")
432
- define_method("#{ref.accessor}=") do |v|
433
- instance_variable_set("@#{ref.accessor}", v)
434
- end
471
+ if writable && !instance_methods.include?("#{attr.accessor}=")
472
+ attr_writer(attr.accessor)
435
473
  end
436
474
  end
437
475
  end
@@ -442,18 +480,43 @@ module ROXML # :nodoc:
442
480
  end
443
481
  deprecate :xml_construction_args
444
482
 
483
+ # A helper which enables us to detect when the xml_name has been explicitly set
484
+ def xml_name? #:nodoc:
485
+ @roxml_tag_name
486
+ end
487
+ deprecate :xml_name?
488
+
445
489
  # Returns the tag name (also known as xml_name) of the class.
446
490
  # If no tag name is set with xml_name method, returns default class name
447
491
  # in lowercase.
492
+ #
493
+ # If xml_convention is set, it is called with an *underscored* version of
494
+ # the class name. This is because active support's inflector generally expects
495
+ # an underscored version, and several operations (e.g. camelcase(:lower), dasherize)
496
+ # do not work without one.
448
497
  def tag_name
449
- @tag_name ||= name.split('::').last.downcase
498
+ return roxml_tag_name if roxml_tag_name
499
+
500
+ if tag_name = name.split('::').last
501
+ roxml_naming_convention ? roxml_naming_convention.call(tag_name.underscore) : tag_name.downcase
502
+ end
503
+ end
504
+
505
+ def roxml_tag_name # :nodoc:
506
+ @roxml_tag_name || superclass.try(:roxml_tag_name)
450
507
  end
451
508
 
452
509
  # Returns array of internal reference objects, such as attributes
453
510
  # and composed XML objects
511
+ def roxml_attrs
512
+ @roxml_attrs ||= []
513
+ (@roxml_attrs + (superclass.try(:roxml_attrs) || [])).freeze
514
+ end
515
+
454
516
  def tag_refs
455
- @xml_refs ||= superclass.respond_to?(:tag_refs) ? superclass.tag_refs.clone : []
517
+ roxml_attrs.map {|a| a.to_ref(nil) }
456
518
  end
519
+ deprecate :tag_refs => :roxml_attrs
457
520
  end
458
521
 
459
522
  module Operations
@@ -479,13 +542,13 @@ module ROXML # :nodoc:
479
542
 
480
543
  unless xml_construction_args_without_deprecation.empty?
481
544
  args = xml_construction_args_without_deprecation.map do |arg|
482
- tag_refs.find {|ref| ref.accessor == arg }
483
- end.map {|ref| ref.value(xml) }
545
+ roxml_attrs.find {|attr| attr.accessor == arg }
546
+ end.map {|attr| attr.to_ref(self).value_in(xml) }
484
547
  new(*args)
485
548
  else
486
549
  returning allocate do |inst|
487
- tag_refs.each do |ref|
488
- ref.populate(xml, inst)
550
+ roxml_attrs.each do |attr|
551
+ inst.instance_variable_set("@#{attr.variable_name}", attr.to_ref(inst).value_in(xml))
489
552
  end
490
553
  inst.send(:xml_initialize, *initialization_args)
491
554
  end