roxml 1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE.txt +9 -0
- data/doc/classes/ROXML.html +352 -0
- data/doc/classes/ROXML.src/M000003.html +19 -0
- data/doc/classes/ROXML.src/M000004.html +25 -0
- data/doc/classes/ROXML.src/M000005.html +22 -0
- data/doc/classes/ROXML/ROXML_Class.html +422 -0
- data/doc/classes/ROXML/ROXML_Class.src/M000006.html +27 -0
- data/doc/classes/ROXML/ROXML_Class.src/M000007.html +18 -0
- data/doc/classes/ROXML/ROXML_Class.src/M000008.html +19 -0
- data/doc/classes/ROXML/ROXML_Class.src/M000009.html +24 -0
- data/doc/classes/ROXML/ROXML_Class.src/M000010.html +24 -0
- data/doc/classes/ROXML/ROXML_Class.src/M000011.html +18 -0
- data/doc/classes/ROXML/ROXML_Class.src/M000012.html +18 -0
- data/doc/classes/ROXML/XMLAttributeRef.html +175 -0
- data/doc/classes/ROXML/XMLAttributeRef.src/M000015.html +19 -0
- data/doc/classes/ROXML/XMLAttributeRef.src/M000016.html +19 -0
- data/doc/classes/ROXML/XMLObjectRef.html +175 -0
- data/doc/classes/ROXML/XMLObjectRef.src/M000013.html +26 -0
- data/doc/classes/ROXML/XMLObjectRef.src/M000014.html +32 -0
- data/doc/classes/ROXML/XMLRef.html +212 -0
- data/doc/classes/ROXML/XMLRef.src/M000017.html +21 -0
- data/doc/classes/ROXML/XMLRef.src/M000018.html +18 -0
- data/doc/classes/ROXML/XMLRef.src/M000019.html +18 -0
- data/doc/classes/ROXML/XMLTextRef.html +193 -0
- data/doc/classes/ROXML/XMLTextRef.src/M000020.html +26 -0
- data/doc/classes/ROXML/XMLTextRef.src/M000021.html +33 -0
- data/doc/classes/String.html +165 -0
- data/doc/classes/String.src/M000001.html +23 -0
- data/doc/classes/String.src/M000002.html +23 -0
- data/doc/created.rid +1 -0
- data/doc/files/lib/roxml_rb.html +234 -0
- data/doc/fr_class_index.html +33 -0
- data/doc/fr_file_index.html +27 -0
- data/doc/fr_method_index.html +47 -0
- data/doc/index.html +24 -0
- data/doc/rdoc-style.css +208 -0
- data/lib/roxml.rb +507 -0
- data/test/fixture_helper.rb +5 -0
- data/test/fixtures/book_malformed.xml +5 -0
- data/test/fixtures/book_pair.xml +8 -0
- data/test/fixtures/book_valid.xml +5 -0
- data/test/fixtures/book_with_contributions.xml +9 -0
- data/test/fixtures/book_with_contributors.xml +7 -0
- data/test/fixtures/book_with_publisher.xml +7 -0
- data/test/fixtures/library.xml +30 -0
- data/test/mocks/mocks.rb +59 -0
- data/test/test_roxml.rb +90 -0
- 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>
|
data/doc/rdoc-style.css
ADDED
@@ -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
|
+
|