roxml 1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/MIT-LICENSE.txt +9 -0
  2. data/doc/classes/ROXML.html +352 -0
  3. data/doc/classes/ROXML.src/M000003.html +19 -0
  4. data/doc/classes/ROXML.src/M000004.html +25 -0
  5. data/doc/classes/ROXML.src/M000005.html +22 -0
  6. data/doc/classes/ROXML/ROXML_Class.html +422 -0
  7. data/doc/classes/ROXML/ROXML_Class.src/M000006.html +27 -0
  8. data/doc/classes/ROXML/ROXML_Class.src/M000007.html +18 -0
  9. data/doc/classes/ROXML/ROXML_Class.src/M000008.html +19 -0
  10. data/doc/classes/ROXML/ROXML_Class.src/M000009.html +24 -0
  11. data/doc/classes/ROXML/ROXML_Class.src/M000010.html +24 -0
  12. data/doc/classes/ROXML/ROXML_Class.src/M000011.html +18 -0
  13. data/doc/classes/ROXML/ROXML_Class.src/M000012.html +18 -0
  14. data/doc/classes/ROXML/XMLAttributeRef.html +175 -0
  15. data/doc/classes/ROXML/XMLAttributeRef.src/M000015.html +19 -0
  16. data/doc/classes/ROXML/XMLAttributeRef.src/M000016.html +19 -0
  17. data/doc/classes/ROXML/XMLObjectRef.html +175 -0
  18. data/doc/classes/ROXML/XMLObjectRef.src/M000013.html +26 -0
  19. data/doc/classes/ROXML/XMLObjectRef.src/M000014.html +32 -0
  20. data/doc/classes/ROXML/XMLRef.html +212 -0
  21. data/doc/classes/ROXML/XMLRef.src/M000017.html +21 -0
  22. data/doc/classes/ROXML/XMLRef.src/M000018.html +18 -0
  23. data/doc/classes/ROXML/XMLRef.src/M000019.html +18 -0
  24. data/doc/classes/ROXML/XMLTextRef.html +193 -0
  25. data/doc/classes/ROXML/XMLTextRef.src/M000020.html +26 -0
  26. data/doc/classes/ROXML/XMLTextRef.src/M000021.html +33 -0
  27. data/doc/classes/String.html +165 -0
  28. data/doc/classes/String.src/M000001.html +23 -0
  29. data/doc/classes/String.src/M000002.html +23 -0
  30. data/doc/created.rid +1 -0
  31. data/doc/files/lib/roxml_rb.html +234 -0
  32. data/doc/fr_class_index.html +33 -0
  33. data/doc/fr_file_index.html +27 -0
  34. data/doc/fr_method_index.html +47 -0
  35. data/doc/index.html +24 -0
  36. data/doc/rdoc-style.css +208 -0
  37. data/lib/roxml.rb +507 -0
  38. data/test/fixture_helper.rb +5 -0
  39. data/test/fixtures/book_malformed.xml +5 -0
  40. data/test/fixtures/book_pair.xml +8 -0
  41. data/test/fixtures/book_valid.xml +5 -0
  42. data/test/fixtures/book_with_contributions.xml +9 -0
  43. data/test/fixtures/book_with_contributors.xml +7 -0
  44. data/test/fixtures/book_with_publisher.xml +7 -0
  45. data/test/fixtures/library.xml +30 -0
  46. data/test/mocks/mocks.rb +59 -0
  47. data/test/test_roxml.rb +90 -0
  48. metadata +104 -0
@@ -0,0 +1,33 @@
1
+
2
+ <?xml version="1.0" encoding="iso-8859-1"?>
3
+ <!DOCTYPE html
4
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
5
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
6
+
7
+ <!--
8
+
9
+ Classes
10
+
11
+ -->
12
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
13
+ <head>
14
+ <title>Classes</title>
15
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
16
+ <link rel="stylesheet" href="rdoc-style.css" type="text/css" />
17
+ <base target="docwin" />
18
+ </head>
19
+ <body>
20
+ <div id="index">
21
+ <h1 class="section-bar">Classes</h1>
22
+ <div id="index-entries">
23
+ <a href="classes/ROXML.html">ROXML</a><br />
24
+ <a href="classes/ROXML/ROXML_Class.html">ROXML::ROXML_Class</a><br />
25
+ <a href="classes/ROXML/XMLAttributeRef.html">ROXML::XMLAttributeRef</a><br />
26
+ <a href="classes/ROXML/XMLObjectRef.html">ROXML::XMLObjectRef</a><br />
27
+ <a href="classes/ROXML/XMLRef.html">ROXML::XMLRef</a><br />
28
+ <a href="classes/ROXML/XMLTextRef.html">ROXML::XMLTextRef</a><br />
29
+ <a href="classes/String.html">String</a><br />
30
+ </div>
31
+ </div>
32
+ </body>
33
+ </html>
@@ -0,0 +1,27 @@
1
+
2
+ <?xml version="1.0" encoding="iso-8859-1"?>
3
+ <!DOCTYPE html
4
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
5
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
6
+
7
+ <!--
8
+
9
+ Files
10
+
11
+ -->
12
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
13
+ <head>
14
+ <title>Files</title>
15
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
16
+ <link rel="stylesheet" href="rdoc-style.css" type="text/css" />
17
+ <base target="docwin" />
18
+ </head>
19
+ <body>
20
+ <div id="index">
21
+ <h1 class="section-bar">Files</h1>
22
+ <div id="index-entries">
23
+ <a href="files/lib/roxml_rb.html">lib/roxml.rb</a><br />
24
+ </div>
25
+ </div>
26
+ </body>
27
+ </html>
@@ -0,0 +1,47 @@
1
+
2
+ <?xml version="1.0" encoding="iso-8859-1"?>
3
+ <!DOCTYPE html
4
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
5
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
6
+
7
+ <!--
8
+
9
+ Methods
10
+
11
+ -->
12
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
13
+ <head>
14
+ <title>Methods</title>
15
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
16
+ <link rel="stylesheet" href="rdoc-style.css" type="text/css" />
17
+ <base target="docwin" />
18
+ </head>
19
+ <body>
20
+ <div id="index">
21
+ <h1 class="section-bar">Methods</h1>
22
+ <div id="index-entries">
23
+ <a href="classes/ROXML.html#M000003">included (ROXML)</a><br />
24
+ <a href="classes/ROXML.html#M000005">method_missing (ROXML)</a><br />
25
+ <a href="classes/ROXML/XMLRef.html#M000017">new (ROXML::XMLRef)</a><br />
26
+ <a href="classes/ROXML/ROXML_Class.html#M000006">parse (ROXML::ROXML_Class)</a><br />
27
+ <a href="classes/ROXML/XMLTextRef.html#M000021">populate (ROXML::XMLTextRef)</a><br />
28
+ <a href="classes/ROXML/XMLRef.html#M000019">populate (ROXML::XMLRef)</a><br />
29
+ <a href="classes/ROXML/XMLAttributeRef.html#M000016">populate (ROXML::XMLAttributeRef)</a><br />
30
+ <a href="classes/ROXML/XMLObjectRef.html#M000014">populate (ROXML::XMLObjectRef)</a><br />
31
+ <a href="classes/ROXML/ROXML_Class.html#M000011">tag_name (ROXML::ROXML_Class)</a><br />
32
+ <a href="classes/ROXML/ROXML_Class.html#M000012">tag_refs (ROXML::ROXML_Class)</a><br />
33
+ <a href="classes/String.html#M000002">to_latin (String)</a><br />
34
+ <a href="classes/String.html#M000001">to_utf (String)</a><br />
35
+ <a href="classes/ROXML.html#M000004">to_xml (ROXML)</a><br />
36
+ <a href="classes/ROXML/XMLAttributeRef.html#M000015">update_xml (ROXML::XMLAttributeRef)</a><br />
37
+ <a href="classes/ROXML/XMLTextRef.html#M000020">update_xml (ROXML::XMLTextRef)</a><br />
38
+ <a href="classes/ROXML/XMLObjectRef.html#M000013">update_xml (ROXML::XMLObjectRef)</a><br />
39
+ <a href="classes/ROXML/XMLRef.html#M000018">update_xml (ROXML::XMLRef)</a><br />
40
+ <a href="classes/ROXML/ROXML_Class.html#M000008">xml_attribute (ROXML::ROXML_Class)</a><br />
41
+ <a href="classes/ROXML/ROXML_Class.html#M000007">xml_name (ROXML::ROXML_Class)</a><br />
42
+ <a href="classes/ROXML/ROXML_Class.html#M000010">xml_object (ROXML::ROXML_Class)</a><br />
43
+ <a href="classes/ROXML/ROXML_Class.html#M000009">xml_text (ROXML::ROXML_Class)</a><br />
44
+ </div>
45
+ </div>
46
+ </body>
47
+ </html>
data/doc/index.html ADDED
@@ -0,0 +1,24 @@
1
+ <?xml version="1.0" encoding="iso-8859-1"?>
2
+ <!DOCTYPE html
3
+ PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
4
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
5
+
6
+ <!--
7
+
8
+ RDoc Documentation
9
+
10
+ -->
11
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
12
+ <head>
13
+ <title>RDoc Documentation</title>
14
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
15
+ </head>
16
+ <frameset rows="20%, 80%">
17
+ <frameset cols="25%,35%,45%">
18
+ <frame src="fr_file_index.html" title="Files" name="Files" />
19
+ <frame src="fr_class_index.html" name="Classes" />
20
+ <frame src="fr_method_index.html" name="Methods" />
21
+ </frameset>
22
+ <frame src="files/lib/roxml_rb.html" name="docwin" />
23
+ </frameset>
24
+ </html>
@@ -0,0 +1,208 @@
1
+
2
+ body {
3
+ font-family: Verdana,Arial,Helvetica,sans-serif;
4
+ font-size: 90%;
5
+ margin: 0;
6
+ margin-left: 40px;
7
+ padding: 0;
8
+ background: white;
9
+ }
10
+
11
+ h1,h2,h3,h4 { margin: 0; color: #efefef; background: transparent; }
12
+ h1 { font-size: 150%; }
13
+ h2,h3,h4 { margin-top: 1em; }
14
+
15
+ a { background: #eef; color: #039; text-decoration: none; }
16
+ a:hover { background: #039; color: #eef; }
17
+
18
+ /* Override the base stylesheet's Anchor inside a table cell */
19
+ td > a {
20
+ background: transparent;
21
+ color: #039;
22
+ text-decoration: none;
23
+ }
24
+
25
+ /* and inside a section title */
26
+ .section-title > a {
27
+ background: transparent;
28
+ color: #eee;
29
+ text-decoration: none;
30
+ }
31
+
32
+ /* === Structural elements =================================== */
33
+
34
+ div#index {
35
+ margin: 0;
36
+ margin-left: -40px;
37
+ padding: 0;
38
+ font-size: 90%;
39
+ }
40
+
41
+
42
+ div#index a {
43
+ margin-left: 0.7em;
44
+ }
45
+
46
+ div#index .section-bar {
47
+ margin-left: 0px;
48
+ padding-left: 0.7em;
49
+ background: #ccc;
50
+ font-size: small;
51
+ }
52
+
53
+
54
+ div#classHeader, div#fileHeader {
55
+ width: auto;
56
+ color: white;
57
+ padding: 0.5em 1.5em 0.5em 1.5em;
58
+ margin: 0;
59
+ margin-left: -40px;
60
+ border-bottom: 3px solid #006;
61
+ }
62
+
63
+ div#classHeader a, div#fileHeader a {
64
+ background: inherit;
65
+ color: white;
66
+ }
67
+
68
+ div#classHeader td, div#fileHeader td {
69
+ background: inherit;
70
+ color: white;
71
+ }
72
+
73
+
74
+ div#fileHeader {
75
+ background: #057;
76
+ }
77
+
78
+ div#classHeader {
79
+ background: #048;
80
+ }
81
+
82
+
83
+ .class-name-in-header {
84
+ font-size: 180%;
85
+ font-weight: bold;
86
+ }
87
+
88
+
89
+ div#bodyContent {
90
+ padding: 0 1.5em 0 1.5em;
91
+ }
92
+
93
+ div#description {
94
+ padding: 0.5em 1.5em;
95
+ background: #efefef;
96
+ border: 1px dotted #999;
97
+ }
98
+
99
+ div#description h1,h2,h3,h4,h5,h6 {
100
+ color: #125;;
101
+ background: transparent;
102
+ }
103
+
104
+ div#validator-badges {
105
+ text-align: center;
106
+ }
107
+ div#validator-badges img { border: 0; }
108
+
109
+ div#copyright {
110
+ color: #333;
111
+ background: #efefef;
112
+ font: 0.75em sans-serif;
113
+ margin-top: 5em;
114
+ margin-bottom: 0;
115
+ padding: 0.5em 2em;
116
+ }
117
+
118
+
119
+ /* === Classes =================================== */
120
+
121
+ table.header-table {
122
+ color: white;
123
+ font-size: small;
124
+ }
125
+
126
+ .type-note {
127
+ font-size: small;
128
+ color: #DEDEDE;
129
+ }
130
+
131
+ .xxsection-bar {
132
+ background: #eee;
133
+ color: #333;
134
+ padding: 3px;
135
+ }
136
+
137
+ .section-bar {
138
+ color: #333;
139
+ border-bottom: 1px solid #999;
140
+ margin-left: -20px;
141
+ }
142
+
143
+
144
+ .section-title {
145
+ background: #79a;
146
+ color: #eee;
147
+ padding: 3px;
148
+ margin-top: 2em;
149
+ margin-left: -30px;
150
+ border: 1px solid #999;
151
+ }
152
+
153
+ .top-aligned-row { vertical-align: top }
154
+ .bottom-aligned-row { vertical-align: bottom }
155
+
156
+ /* --- Context section classes ----------------------- */
157
+
158
+ .context-row { }
159
+ .context-item-name { font-family: monospace; font-weight: bold; color: black; }
160
+ .context-item-value { font-size: small; color: #448; }
161
+ .context-item-desc { color: #333; padding-left: 2em; }
162
+
163
+ /* --- Method classes -------------------------- */
164
+ .method-detail {
165
+ background: #efefef;
166
+ padding: 0;
167
+ margin-top: 0.5em;
168
+ margin-bottom: 1em;
169
+ border: 1px dotted #ccc;
170
+ }
171
+ .method-heading {
172
+ color: black;
173
+ background: #ccc;
174
+ border-bottom: 1px solid #666;
175
+ padding: 0.2em 0.5em 0 0.5em;
176
+ }
177
+ .method-signature { color: black; background: inherit; }
178
+ .method-name { font-weight: bold; }
179
+ .method-args { font-style: italic; }
180
+ .method-description { padding: 0 0.5em 0 0.5em; }
181
+
182
+ /* --- Source code sections -------------------- */
183
+
184
+ a.source-toggle { font-size: 90%; }
185
+ div.method-source-code {
186
+ background: #262626;
187
+ color: #ffdead;
188
+ margin: 1em;
189
+ padding: 0.5em;
190
+ border: 1px dashed #999;
191
+ overflow: hidden;
192
+ }
193
+
194
+ div.method-source-code pre { color: #ffdead; overflow: hidden; }
195
+
196
+ /* --- Ruby keyword styles --------------------- */
197
+
198
+ .standalone-code { background: #221111; color: #ffdead; overflow: hidden; }
199
+
200
+ .ruby-constant { color: #7fffd4; background: transparent; }
201
+ .ruby-keyword { color: #00ffff; background: transparent; }
202
+ .ruby-ivar { color: #eedd82; background: transparent; }
203
+ .ruby-operator { color: #00ffee; background: transparent; }
204
+ .ruby-identifier { color: #ffdead; background: transparent; }
205
+ .ruby-node { color: #ffa07a; background: transparent; }
206
+ .ruby-comment { color: #b22222; font-weight: bold; background: transparent; }
207
+ .ruby-regexp { color: #ffa07a; background: transparent; }
208
+ .ruby-value { color: #7fffd4; background: transparent; }
data/lib/roxml.rb ADDED
@@ -0,0 +1,507 @@
1
+ # ROXML Ruby Object to XML mapping library. For more information
2
+ # visit http://roxml.rubyforge.org
3
+ #
4
+ # Copyright (c) 2004-2006 Zak Mandhro and Anders Engstrom
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
7
+ #
8
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
9
+ #
10
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
11
+ #
12
+ # =Quick Start Guide
13
+ #
14
+ # This is a short usage example. See ROXML::ROXML_Class and packaged test cases for more information.
15
+ #
16
+ # Consider an XML document representing a Library containing a number of Books. You
17
+ # can map this structure to Ruby classes that provide addition useful behavior. With
18
+ # ROXML, you can annotate the Ruby classes as follows:
19
+ #
20
+ # class Book
21
+ # include ROXML
22
+ #
23
+ # xml_attribute :isbn, "ISBN"
24
+ # xml_text :title
25
+ # xml_text :description, nil, ROXML::TAG_CDATA
26
+ # xml_text :author
27
+ # end
28
+ #
29
+ # class Library
30
+ # include ROXML
31
+ #
32
+ # xml_text :name, "NAME", ROXML::TAG_CDATA
33
+ # xml_object :books, Book, ROXML::TAG_ARRAY, "books"
34
+ # end
35
+ #
36
+ # To create a library and put a number of books in it we could run the following code:
37
+ #
38
+ # book = Book.new()
39
+ # book.isbn = "0201710897"
40
+ # book.title = "The PickAxe"
41
+ # book.description = "Best Ruby book out there!"
42
+ # book.author = "David Thomas, Andrew Hunt, Dave Thomas"
43
+ #
44
+ # lib = Library.new()
45
+ # lib.name = "Favorite Books"
46
+ # lib << book
47
+ #
48
+ # To save this information to an XML file:
49
+ #
50
+ # File.open("library.xml", "w") do |f|
51
+ # lib.to_xml.write(f, 0)
52
+ # end
53
+ #
54
+ # To later populate the library object from the XML file:
55
+ #
56
+ # lib = Library.parse(File.read("library.xml"))
57
+ #
58
+ # Similarly, to do a one-to-one mapping between XML objects, such as book and publisher,
59
+ # you would use the *xml_object* annotation. For example:
60
+ #
61
+ # <book isbn="0974514055">
62
+ # <title>Programming Ruby - 2nd Edition</title>
63
+ # <description>Second edition of the great book.</description>
64
+ # <publisher>
65
+ # <name>Pragmatic Bookshelf</name>
66
+ # </publisher>
67
+ # </book>
68
+ #
69
+ # can be mapped using the following code:
70
+ #
71
+ # class BookWithPublisher
72
+ # include ROXML
73
+ #
74
+ # xml_name :book
75
+ # xml_object :publisher, Publisher
76
+ # end
77
+ #
78
+ # Note: In the above example, _xml_name_ annotation tells ROXML to set the element
79
+ # name to "book" for mapping to XML. The default is XML element name is the class name in lowercase; "bookwithpublisher"
80
+ # in this case.
81
+ #
82
+ # For more information on available annotations, see ROXML::ROXML_Class
83
+ module ROXML
84
+
85
+ require 'rexml/document'
86
+
87
+ # Option that may be used to declare that
88
+ # a variable accessor should be read-only (no "accessor=(val)" is generated).
89
+ TAG_READONLY = 1
90
+
91
+ # Option that declares that an XML text element should be
92
+ # wrapped in a CDATA section.
93
+ TAG_CDATA = 2
94
+
95
+ # Option that declares an accessor as an array (referencing "many"
96
+ # items).
97
+ TAG_ARRAY = 4
98
+
99
+ #
100
+ # Internal base class that represents an XML - Class binding.
101
+ #
102
+ class XMLRef
103
+ attr_accessor :accessor, :name, :array
104
+
105
+ def initialize(accessor, name = nil)
106
+ @accessor = accessor
107
+ @name = (name || accessor.id2name)
108
+ yield self if block_given?
109
+ @array = false unless @array
110
+ end
111
+
112
+ # Converts this XML reference to XML and updates the
113
+ # passed in element (XML) with data.
114
+ #
115
+ # <b>Returns</b>: The updated XML node.
116
+ def update_xml(xml, value)
117
+ xml
118
+ end
119
+
120
+ # Reads data from the XML element and populates the object
121
+ # instance accordingly.
122
+ #
123
+ # <b>Returns</b>: The updated instance.
124
+ def populate(xml, instance)
125
+ instance
126
+ end
127
+ end
128
+
129
+ # Interal class representing an XML attribute binding
130
+ #
131
+ # In context:
132
+ # <element attribute="XMLAttributeRef">
133
+ # XMLTextRef
134
+ # </element>
135
+ class XMLAttributeRef < XMLRef
136
+ # Updates the attribute in the given XML block to
137
+ # the value provided.
138
+ def update_xml(xml, value)
139
+ xml.attributes[name] = value.to_s.to_utf
140
+ xml
141
+ end
142
+
143
+ # Reads data from the XML element and populates the object
144
+ # instance accordingly.
145
+ def populate(xml, instance)
146
+ instance.instance_variable_set("@#{accessor}", xml.attributes[name])
147
+ instance
148
+ end
149
+ end
150
+
151
+ # Interal class representing XML content text binding
152
+ #
153
+ # In context:
154
+ # <element attribute="XMLAttributeRef">
155
+ # XMLTextRef
156
+ # </element>
157
+ class XMLTextRef < XMLAttributeRef
158
+ attr_accessor :cdata, :wrapper
159
+
160
+ # Updates the text in the given _xml_ block to
161
+ # the _value_ provided.
162
+ def update_xml(xml, value)
163
+ parent = (wrapper ? xml.add_element(wrapper) : xml)
164
+ if array
165
+ value.each do |v|
166
+ parent.add_element(name).text = (cdata ? REXML::CData.new(v.to_s.to_utf) : v.to_s.to_utf)
167
+ end
168
+ else
169
+ parent.add_element(name).text = (cdata ? REXML::CData.new(value.to_s.to_utf) : value.to_s.to_utf)
170
+ end
171
+ xml
172
+ end
173
+
174
+ # Reads data from the XML element and populates the text
175
+ # accordingly.
176
+ def populate(xml, instance)
177
+ data = nil
178
+ unless array
179
+ child = xml.elements[1, name]
180
+ data = child.text if child && child.text
181
+ else
182
+ xpath = (wrapper ? "#{wrapper}/#{name}" : "#{name}")
183
+ data = []
184
+ xml.each_element(xpath) do |e|
185
+ if e.text
186
+ data << e.text.strip.to_latin
187
+ end
188
+ end
189
+
190
+ end
191
+ instance.instance_variable_set("@#{accessor}", data) if data
192
+ instance
193
+ end
194
+ end
195
+
196
+ class XMLObjectRef < XMLTextRef
197
+ attr_accessor :klass
198
+
199
+ # Updates the composed XML object in the given XML block to
200
+ # the value provided.
201
+ def update_xml(xml, value)
202
+ parent = (wrapper ? xml.add_element(wrapper) : xml)
203
+ unless array
204
+ parent.add_element(value.to_xml)
205
+ else
206
+ value.each do |v|
207
+ parent.add_element(v.to_xml)
208
+ end
209
+ end
210
+ xml
211
+ end
212
+
213
+ # Reads data from the XML element and populates the references XML
214
+ # object accordingly.
215
+ def populate(xml, instance)
216
+ data = nil
217
+ unless array
218
+ child = xml.elements[1, klass.tag_name]
219
+ if child
220
+ data = klass.parse(child)
221
+ end
222
+ else
223
+ xpath = (wrapper ? "#{wrapper}/#{klass.tag_name}" : "#{klass.tag_name}")
224
+ data = []
225
+ xml.each_element(xpath) do |e|
226
+ data << klass.parse(e)
227
+ end
228
+ end
229
+ instance.instance_variable_set("@#{accessor}", data) if data
230
+ instance
231
+ end
232
+ end
233
+
234
+
235
+ # This class defines the annotation methods that are mixed into your
236
+ # Ruby classes for XML mapping information and behavior.
237
+ #
238
+ # See xml_name, xml_text, xml_attribute and xml_object for available
239
+ # annotations.
240
+ #
241
+ module ROXML_Class
242
+ #
243
+ # Creates a new Ruby object from XML using mapping information
244
+ # annotated in the class.
245
+ #
246
+ # The input data is either a REXML::Element or a String representing
247
+ # the XML document.
248
+ #
249
+ # Example
250
+ # book = Book.parse(File.read("book.xml"))
251
+ # or
252
+ # book = Book.parse("<book><name>Beyond Java</name></book>")
253
+ #
254
+ def parse(data)
255
+
256
+ xml = (data.kind_of?(REXML::Element) ? data : REXML::Document.new(data).root)
257
+
258
+ inst = self.allocate
259
+
260
+ tag_refs.each do |ref|
261
+ ref.populate(xml, inst)
262
+ end
263
+
264
+ return inst
265
+ end
266
+
267
+ # Sets the name of the XML element that represents this class. Use this
268
+ # to override the default lowercase class name.
269
+ #
270
+ # Example:
271
+ # class BookWithPublisher
272
+ # xml_name :book
273
+ # end
274
+ #
275
+ # Without the xml_name annotation, the XML mapped tag would have been "bookwithpublisher".
276
+ #
277
+ def xml_name(name)
278
+ @tag_name = name
279
+ end
280
+
281
+ #
282
+ # Declare an accessor for the included class that should be
283
+ # represented as an XML attribute.
284
+ #
285
+ # [sym] Symbol representing the name of the accessor
286
+ # [name] An optional name that should be used for the attribute in XML.
287
+ # Default is sym.id2name.
288
+ # [options] Valid options are TAG_READONLY to attribute as read-only
289
+ #
290
+ # Example:
291
+ # class Book
292
+ # xml_attribute :isbn, "ISBN"
293
+ # end
294
+ #
295
+ # To map:
296
+ # <book ISBN="0974514055"></book>
297
+ #
298
+ def xml_attribute(sym, name = nil, options = 0)
299
+ add_ref(XMLAttributeRef.new(sym, name))
300
+ add_accessor(sym, (TAG_READONLY & options != TAG_READONLY))
301
+ end
302
+
303
+ #
304
+ # Declares an accessor that represents one or more XML text elements.
305
+ #
306
+ # [sym] Symbol representing the name of the accessor.
307
+ # [name] An optional name that should be used for the attribute in XML.
308
+ # Default is sym.id2name.
309
+ # [options] TAG_CDATA for character data, TAG_ARRAY for one-to-many, and
310
+ # TAG_READONLY for read-only access.
311
+ # [wrapper] An optional name of a wrapping tag for this XML accessor.
312
+ #
313
+ # Example:
314
+ # class Book
315
+ # xml_text :description, nil, ROXML::TAG_CDATA
316
+ # end
317
+ #
318
+ # To map:
319
+ # <book>
320
+ # <description><![CDATA[Probably the best Ruby book out there]]></description>
321
+ # </book>
322
+ def xml_text(sym, name = nil, options = 0, wrapper = nil)
323
+ ref = XMLTextRef.new(sym, name) do |r|
324
+ r.cdata = (TAG_CDATA & options == TAG_CDATA)
325
+ r.array = (TAG_ARRAY & options == TAG_ARRAY)
326
+ r.wrapper = wrapper if wrapper
327
+ end
328
+ add_ref(ref)
329
+ add_accessor(sym, (TAG_READONLY & options != TAG_READONLY), ref.array)
330
+ end
331
+
332
+ #
333
+ # Declares an accessor that represents another ROXML class as child XML element
334
+ # (one-to-one or composition) or array of child elements (one-to-many or
335
+ # aggregation). Default is one-to-one. Use TAG_ARRAY option for one-to-many.
336
+ #
337
+ # [sym] Symbol representing the name of the accessor.
338
+ # [name] An optional name that should be used for the attribute in XML.
339
+ # Default is sym.id2name.
340
+ # [options] TAG_ARRAY for one-to-many, and TAG_READONLY for read-only access.
341
+ # [wrapper] An optional name of a wrapping tag for this XML accessor.
342
+ #
343
+ # Composition example:
344
+ # <book>
345
+ # <publisher>
346
+ # <name>Pragmatic Bookshelf</name>
347
+ # </publisher>
348
+ # </book>
349
+ #
350
+ # Can be mapped using the following code:
351
+ # class Book
352
+ # xml_object :publisher, Publisher
353
+ # end
354
+ #
355
+ # Aggregation example:
356
+ # <library>
357
+ # <name>Ruby books</name>
358
+ # <books>
359
+ # <book/>
360
+ # <book/>
361
+ # </books>
362
+ # </library>
363
+ #
364
+ # Can be mapped using the following code:
365
+ # class Library
366
+ # xml_text :name, nil, ROXML::TAG_CDATA
367
+ # xml_object :books, Book, ROXML::TAG_ARRAY, "books"
368
+ # end
369
+ #
370
+ # If you don't have the <books> tag to wrap around the list of <book> tags:
371
+ # <library>
372
+ # <name>Ruby books</name>
373
+ # <book/>
374
+ # <book/>
375
+ # </library>
376
+ #
377
+ # You can skip the wrapper argument:
378
+ # xml_object :books, Book, ROXML::TAG_ARRAY
379
+ #
380
+ def xml_object(sym, klass, options = 0, wrapper = nil)
381
+ ref = XMLObjectRef.new(sym, nil) do |r|
382
+ r.array = (TAG_ARRAY & options == TAG_ARRAY)
383
+ r.wrapper = wrapper if wrapper
384
+ r.klass = klass
385
+ end
386
+ add_ref(ref)
387
+ add_accessor(sym, (TAG_READONLY & options != TAG_READONLY), ref.array)
388
+ end
389
+
390
+ # Returns the tag name (also known as xml_name) of the class.
391
+ # If no tag name is set with xml_name method, returns default class name
392
+ # in lowercase.
393
+ def tag_name
394
+ @tag_name || self.name.downcase
395
+ end
396
+
397
+ # Returns array of internal reference objects, such as attributes
398
+ # and composed XML objects
399
+ def tag_refs
400
+ @xml_refs || []
401
+ end
402
+
403
+ private
404
+
405
+ def add_ref(xml_ref)
406
+ @xml_refs = [] unless @xml_refs
407
+ @xml_refs << xml_ref
408
+ end
409
+
410
+ def assert_accessor(name)
411
+ @tag_accessors = [] unless @tag_accessors
412
+ raise "Accessor #{name} is already defined as XML accessor in class #{self}" if @tag_accessors.include?(name)
413
+ @tag_accessors << name
414
+ end
415
+
416
+ def add_accessor(name, writable = true, is_array = false)
417
+ assert_accessor(name)
418
+ unless instance_methods.include?(name)
419
+ define_method(name) do
420
+ val = instance_variable_get("@#{name}")
421
+ if val.nil? && is_array
422
+ val = Array.new
423
+ instance_variable_set("@#{name}", val)
424
+ end
425
+ val
426
+ end
427
+ end
428
+ if writable
429
+ unless instance_methods.include?("#{name}=")
430
+ define_method("#{name}=") do |v|
431
+ instance_variable_set("@#{name}", v)
432
+ end
433
+ end
434
+ end
435
+ end
436
+
437
+ end ## End ROXML_Class module ##############
438
+
439
+ class << self
440
+ #
441
+ # Extends the klass with the ROXML_Class module methods.
442
+ #
443
+ def included(klass)
444
+ super
445
+ klass.__send__(:extend, ROXML_Class)
446
+ end
447
+ end
448
+
449
+ #
450
+ # Returns an REXML::Element representing this object.
451
+ #
452
+ def to_xml
453
+ root = REXML::Element.new(tag_name)
454
+ tag_refs.each do |ref|
455
+ v = __send__(ref.accessor)
456
+ if v
457
+ root = ref.update_xml(root, v)
458
+ end
459
+ end
460
+ root
461
+ end
462
+
463
+ #
464
+ # To make it easier to reference the class's
465
+ # attributes all method calls to the instance that
466
+ # doesn't match an instance method are forwarded to the
467
+ # class's singleton instance. Only methods starting with 'tag_' are delegated.
468
+ def method_missing(name, *args)
469
+ if name.id2name =~ /^tag_/
470
+ self.class.__send__(name, *args)
471
+ else
472
+ super
473
+ end
474
+ end
475
+
476
+ # Extension of String class to handle conversion from/to
477
+ # UTF-8/ISO-8869-1
478
+ class ::String
479
+ require 'iconv'
480
+
481
+ #
482
+ # Return an utf-8 representation of this string.
483
+ #
484
+ def to_utf
485
+ begin
486
+ Iconv.new("utf-8", "iso-8859-1").iconv(self)
487
+ rescue Iconv::IllegalSequence => e
488
+ STDERR << "!! Failed converting from UTF-8 -> ISO-8859-1 (#{self}). Already the right charset?"
489
+ self
490
+ end
491
+ end
492
+
493
+ #
494
+ # Convert this string to iso-8850-1
495
+ #
496
+ def to_latin
497
+ begin
498
+ Iconv.new("iso-8859-1", "utf-8").iconv(self)
499
+ rescue Iconv::IllegalSequence => e
500
+ STDERR << "!! Failed converting from ISO-8859-1 -> UTF-8 (#{self}). Already the right charset?"
501
+ self
502
+ end
503
+ end
504
+ end
505
+
506
+ end
507
+