xml-mapping 0.9.1 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +10 -53
  3. data/README.md +57 -0
  4. data/Rakefile +75 -85
  5. data/TODO.txt +12 -2
  6. data/examples/README +4 -4
  7. data/examples/cleanup.rb +11 -0
  8. data/examples/company_usage.intout +7 -7
  9. data/examples/documents_folders_usage.intout +2 -2
  10. data/examples/order_signature_enhanced_usage.intout +3 -3
  11. data/examples/order_usage.intout +26 -26
  12. data/examples/person.intout +3 -3
  13. data/examples/person_mm.intout +2 -2
  14. data/examples/publication.intout +2 -2
  15. data/examples/reader.intout +1 -1
  16. data/examples/stringarray_usage.intout +1 -1
  17. data/examples/time_augm.intout +6 -6
  18. data/examples/xpath_create_new.intout +13 -13
  19. data/examples/xpath_ensure_created.intout +2 -2
  20. data/examples/xpath_usage.intout +1 -1
  21. data/lib/xml/mapping.rb +0 -2
  22. data/lib/xml/mapping/base.rb +24 -9
  23. data/lib/xml/mapping/version.rb +1 -1
  24. data/test/company.rb +43 -0
  25. data/test/documents_folders.rb +7 -0
  26. data/test/multiple_mappings_test.rb +3 -1
  27. data/test/xml_mapping_adv_test.rb +20 -20
  28. data/test/xml_mapping_test.rb +31 -2
  29. data/{README → user_manual.md} +916 -154
  30. data/user_manual_xxpath.md +677 -0
  31. metadata +100 -112
  32. data/ChangeLog +0 -189
  33. data/README_XPATH +0 -202
  34. data/examples/company_usage.intin.rb +0 -19
  35. data/examples/documents_folders_usage.intin.rb +0 -18
  36. data/examples/order_signature_enhanced_usage.intin.rb +0 -12
  37. data/examples/order_usage.intin.rb +0 -120
  38. data/examples/person.intin.rb +0 -44
  39. data/examples/person_mm.intin.rb +0 -119
  40. data/examples/publication.intin.rb +0 -44
  41. data/examples/reader.intin.rb +0 -33
  42. data/examples/stringarray_usage.intin.rb +0 -11
  43. data/examples/time_augm.intin.rb +0 -19
  44. data/examples/time_augm_loading.intin.rb +0 -44
  45. data/examples/time_node.intin.rb +0 -79
  46. data/examples/time_node_w_marshallers.intin.rb +0 -48
  47. data/examples/xpath_create_new.intin.rb +0 -85
  48. data/examples/xpath_docvsroot.intin.rb +0 -30
  49. data/examples/xpath_ensure_created.intin.rb +0 -62
  50. data/examples/xpath_pathological.intin.rb +0 -42
  51. data/examples/xpath_usage.intin.rb +0 -51
  52. data/install.rb +0 -41
  53. data/test/xxpath_benchmark.result1.txt +0 -17
@@ -15,7 +15,9 @@ class MultipleMappingsTest < Test::Unit::TestCase
15
15
  Classes_by_rootelt_names.clear
16
16
  EOS
17
17
  Object.send(:remove_const, "Triangle")
18
- $".delete "triangle_mm.rb"
18
+ unless ($".delete "triangle_mm.rb")
19
+ $".delete_if{|name| name =~ %r!test/triangle_mm.rb$!}
20
+ end
19
21
  $:.unshift File.dirname(__FILE__) # test/unit may have undone this (see test/unit/collector/dir.rb)
20
22
  require 'triangle_mm'
21
23
  end
@@ -3,7 +3,6 @@ require File.dirname(__FILE__)+"/tests_init"
3
3
  require 'test/unit'
4
4
  require 'documents_folders'
5
5
  require 'bookmarks'
6
- require 'yaml'
7
6
 
8
7
  class XmlMappingAdvancedTest < Test::Unit::TestCase
9
8
  def setup
@@ -13,8 +12,12 @@ class XmlMappingAdvancedTest < Test::Unit::TestCase
13
12
  Object.send(:remove_const, "Document")
14
13
  Object.send(:remove_const, "Folder")
15
14
 
16
- $".delete "documents_folders.rb"
17
- $".delete "bookmarks.rb"
15
+ unless ($".delete "documents_folders.rb") # works in 1.8 only. In 1.9, $" contains absolute paths.
16
+ $".delete_if{|name| name =~ %r!test/documents_folders.rb$!}
17
+ end
18
+ unless ($".delete "bookmarks.rb")
19
+ $".delete_if{|name| name =~ %r!test/bookmarks.rb$!}
20
+ end
18
21
  require 'documents_folders'
19
22
  require 'bookmarks'
20
23
 
@@ -26,23 +29,20 @@ class XmlMappingAdvancedTest < Test::Unit::TestCase
26
29
  end
27
30
 
28
31
  def test_read_polymorphic_object
29
- assert_equal YAML::load(<<-EOS), @f
30
- --- !ruby/object:Folder
31
- entries:
32
- - !ruby/object:Document
33
- contents: " inhale, exhale"
34
- name: plan
35
- - !ruby/object:Folder
36
- entries:
37
- - !ruby/object:Folder
38
- entries:
39
- - !ruby/object:Document
40
- contents: foo bar baz
41
- name: README
42
- name: xml-mapping
43
- name: work
44
- name: home
45
- EOS
32
+ expected = Folder.new \
33
+ :name => "home",
34
+ :entries => [
35
+ Document.new(:name => "plan", :contents => " inhale, exhale"),
36
+ Folder.new(:name => "work",
37
+ :entries => [
38
+ Folder.new(:name => "xml-mapping",
39
+ :entries => [Document.new(:name => "README",
40
+ :contents => "foo bar baz")]
41
+ )
42
+ ])
43
+ ]
44
+
45
+ assert_equal expected, @f
46
46
  end
47
47
 
48
48
  def test_write_polymorphic_object
@@ -27,7 +27,10 @@ class XmlMappingTest < Test::Unit::TestCase
27
27
  Object.send(:remove_const, "Names1")
28
28
  Object.send(:remove_const, "ReaderTest")
29
29
  Object.send(:remove_const, "WriterTest")
30
- $".delete "company.rb"
30
+ Object.send(:remove_const, "ReaderWriterProcVsLambdaTest")
31
+ unless ($".delete "company.rb") # works in 1.8 only. In 1.9, $" contains absolute paths.
32
+ $".delete_if{|name| name =~ %r!test/company.rb$!}
33
+ end
31
34
  $:.unshift File.dirname(__FILE__) # test/unit may have undone this (see test/unit/collector/dir.rb)
32
35
  require 'company'
33
36
 
@@ -155,7 +158,33 @@ class XmlMappingTest < Test::Unit::TestCase
155
158
  assert_equal %w{dingdong2 dingdong3}, xml2.all_xpath("quux").map{|elt|elt.text}
156
159
  end
157
160
 
158
-
161
+ def test_reader_writer_proc_vs_lambda
162
+ xml = REXML::Document.new("<test>
163
+ <proc_2args>proc_2args_text</proc_2args>
164
+ <lambda_2args>lambda_2args_text</lambda_2args>
165
+ <proc_3args>proc_3args_text</proc_3args>
166
+ <lambda_3args>lambda_3args_text</lambda_3args>
167
+ </test>").root
168
+ r = ReaderWriterProcVsLambdaTest.load_from_xml xml
169
+ assert_equal [:proc_2args, :proc_3args, :lambda_2args, :lambda_3args], r.read
170
+ assert_nil r.written
171
+ assert_nil r.proc_2args
172
+ assert_nil r.lambda_2args
173
+ assert_equal 'proc_3args_text', r.proc_3args
174
+ assert_equal 'lambda_3args_text', r.lambda_3args
175
+
176
+ r.proc_2args = "proc_2args_text_new"
177
+ r.lambda_2args = "lambda_2args_text_new"
178
+ r.proc_3args = "proc_3args_text_new"
179
+ r.lambda_3args = "lambda_3args_text_new"
180
+ xml2 = r.save_to_xml
181
+ assert_equal [:proc_2args, :proc_3args, :lambda_2args, :lambda_3args], r.written
182
+ assert_nil xml2.first_xpath("proc_2args", :allow_nil=>true)
183
+ assert_nil xml2.first_xpath("lambda_2args", :allow_nil=>true)
184
+ assert_equal 'proc_3args_text_new', xml2.first_xpath("proc_3args").text
185
+ assert_equal 'lambda_3args_text_new', xml2.first_xpath("lambda_3args").text
186
+ end
187
+
159
188
  def test_setter_text_node
160
189
  @c.ent2 = "lalala"
161
190
  assert_equal "lalala", REXML::XPath.first(@c.save_to_xml, "arrtest/entry[2]").text
@@ -1,44 +1,377 @@
1
- = XML-MAPPING: XML-to-object (and back) Mapper for Ruby, including XPath Interpreter
1
+ # XML-MAPPING: XML-to-object (and back) Mapper for Ruby, including XPath Interpreter
2
2
 
3
3
  Xml-mapping is an easy to use, extensible library that allows you to
4
4
  semi-automatically map Ruby objects to XML trees and vice versa.
5
5
 
6
- == Download
6
+ ## Download
7
7
 
8
- For downloading the latest version, CVS repository access etc. go to:
8
+ For downloading the latest version, git repository access etc. go to:
9
9
 
10
- http://rubyforge.org/projects/xml-mapping/
10
+ https://github.com/multi-io/xml-mapping
11
11
 
12
- == Contents of this Document
12
+ ## Contents of this Document
13
13
 
14
- - {Example}[aref:example]
15
- - {Single-attribute Nodes}[aref:sanodes]
16
- - {Default Values}[aref:defaultvalues]
17
- - {Single-attribute Nodes with Sub-objects}[aref:subobjnodes]
18
- - {Attribute Handling Details, Augmenting Existing Classes}[aref:attrdefns]
19
- - {Other Nodes}[aref:onodes]
20
- - {choice_node}[aref:choice_node]
21
- - {Readers/Writers}[aref:readerswriters]
22
- - {Multiple Mappings per Class}[aref:mappings]
23
- - {Defining your own Node Types}[aref:definingnodes]
24
- - {XPath Interpreter}[aref:xpath]
14
+ - {Example}[rdoc-label:label-Example]
15
+ - {Single-attribute Nodes}[rdoc-label:label-Single-attribute+Nodes]
16
+ - {Default Values}[rdoc-label:label-Default+Values]
17
+ - {Single-attribute Nodes with Sub-objects}[rdoc-label:label-Single-attribute+Nodes+with+Sub-objects]
18
+ - {Attribute Handling Details, Augmenting Existing Classes}[rdoc-label:label-Attribute+Handling+Details%2C+Augmenting+Existing+Classes]
19
+ - {Other Nodes}[rdoc-label:label-Other+Nodes]
20
+ - {choice_node}[rdoc-label:label-choice_node]
21
+ - {Readers/Writers}[rdoc-label:label-Readers%2FWriters]
22
+ - {Multiple Mappings per Class}[rdoc-label:label-Multiple+Mappings+per+Class]
23
+ - {Defining your own Node Types}[rdoc-label:label-Defining+your+own+Node+Types]
24
+ - {XPath Interpreter}[rdoc-label:label-XPath+Interpreter]
25
25
 
26
- == {Example}[a:example]
26
+ ## Example
27
27
 
28
28
  (example document stolen + extended from
29
29
  http://www.castor.org/xml-mapping.html)
30
30
 
31
- === Input Document:
32
-
33
- :include: order.xml
34
-
35
- === Mapping Class Declaration:
36
-
37
- :include: order.rb
38
-
39
- === Usage:
40
-
41
- :include: order_usage.intout
31
+ ### Input Document:
32
+
33
+ <?xml version="1.0" encoding="ISO-8859-1"?>
34
+
35
+ <Order reference="12343-AHSHE-314159">
36
+ <Client>
37
+ <Name>Jean Smith</Name>
38
+ <Address where="home">
39
+ <City>San Mateo</City>
40
+ <State>CA</State>
41
+ <ZIP>94403</ZIP>
42
+ <Street>2000, Alameda de las Pulgas</Street>
43
+ </Address>
44
+ <Address where="work">
45
+ <City>San Francisco</City>
46
+ <State>CA</State>
47
+ <ZIP>94102</ZIP>
48
+ <Street>98765, Fulton Street</Street>
49
+ </Address>
50
+ </Client>
51
+
52
+ <Item reference="RF-0001">
53
+ <Description>Stuffed Penguin</Description>
54
+ <Quantity>10</Quantity>
55
+ <UnitPrice>8.95</UnitPrice>
56
+ </Item>
57
+
58
+ <Item reference="RF-0034">
59
+ <Description>Chocolate</Description>
60
+ <Quantity>5</Quantity>
61
+ <UnitPrice>28.50</UnitPrice>
62
+ </Item>
63
+
64
+ <Item reference="RF-3341">
65
+ <Description>Cookie</Description>
66
+ <Quantity>30</Quantity>
67
+ <UnitPrice>0.85</UnitPrice>
68
+ </Item>
69
+
70
+ <Signed-By>
71
+ <Signature>
72
+ <Name>John Doe</Name>
73
+ <Position>product manager</Position>
74
+ </Signature>
75
+
76
+ <Signature>
77
+ <Name>Jill Smith</Name>
78
+ <Position>clerk</Position>
79
+ </Signature>
80
+
81
+ <Signature>
82
+ <Name>Miles O'Brien</Name>
83
+ </Signature>
84
+ </Signed-By>
85
+
86
+ </Order>
87
+
88
+ ### Mapping Class Declaration:
89
+
90
+ require 'xml/mapping'
91
+
92
+ ## forward declarations
93
+ class Client; end
94
+ class Address; end
95
+ class Item; end
96
+ class Signature; end
97
+
98
+
99
+ class Order
100
+ include XML::Mapping
101
+
102
+ text_node :reference, "@reference"
103
+ object_node :client, "Client", :class=>Client
104
+ hash_node :items, "Item", "@reference", :class=>Item
105
+ array_node :signatures, "Signed-By", "Signature", :class=>Signature, :default_value=>[]
106
+
107
+ def total_price
108
+ items.values.map{|i| i.total_price}.inject(0){|x,y|x+y}
109
+ end
110
+ end
111
+
112
+
113
+ class Client
114
+ include XML::Mapping
115
+
116
+ text_node :name, "Name"
117
+ object_node :home_address, "Address[@where='home']", :class=>Address
118
+ object_node :work_address, "Address[@where='work']", :class=>Address, :default_value=>nil
119
+ end
120
+
121
+
122
+ class Address
123
+ include XML::Mapping
124
+
125
+ text_node :city, "City"
126
+ text_node :state, "State"
127
+ numeric_node :zip, "ZIP"
128
+ text_node :street, "Street"
129
+ end
130
+
131
+
132
+ class Item
133
+ include XML::Mapping
134
+
135
+ text_node :descr, "Description"
136
+ numeric_node :quantity, "Quantity"
137
+ numeric_node :unit_price, "UnitPrice"
138
+
139
+ def total_price
140
+ quantity*unit_price
141
+ end
142
+ end
143
+
144
+
145
+ class Signature
146
+ include XML::Mapping
147
+
148
+ text_node :name, "Name"
149
+ text_node :position, "Position", :default_value=>"Some Employee"
150
+ end
151
+
152
+ ### Usage:
153
+
154
+ ####read access
155
+ o=Order.load_from_file("order.xml")
156
+ => #<Order:0x007fb01549d438 @signatures=[#<Signature:0x007fb015493b68 @position="product manager", @name="John Doe">, #<Signature:0x007fb015492c68 @position="clerk", @name="Jill Smith">, #<Signature:0x007fb015491a20 @position="Some Employee", @name="Miles O'Brien">], @reference="12343-AHSHE-314159", @client=#<Client:0x007fb01549c998 @work_address=#<Address:0x007fb01549a238 @city="San Francisco", @state="CA", @zip=94102, @street="98765, Fulton Street">, @name="Jean Smith", @home_address=#<Address:0x007fb01549bbb0 @city="San Mateo", @state="CA", @zip=94403, @street="2000, Alameda de las Pulgas">>, @items={"RF-0001"=>#<Item:0x007fb0154989b0 @descr="Stuffed Penguin", @quantity=10, @unit_price=8.95>, "RF-0034"=>#<Item:0x007fb015496638 @descr="Chocolate", @quantity=5, @unit_price=28.5>, "RF-3341"=>#<Item:0x007fb015495300 @descr="Cookie", @quantity=30, @unit_price=0.85>}>
157
+ o.reference
158
+ => "12343-AHSHE-314159"
159
+ o.client
160
+ => #<Client:0x007fb01549c998 @work_address=#<Address:0x007fb01549a238 @city="San Francisco", @state="CA", @zip=94102, @street="98765, Fulton Street">, @name="Jean Smith", @home_address=#<Address:0x007fb01549bbb0 @city="San Mateo", @state="CA", @zip=94403, @street="2000, Alameda de las Pulgas">>
161
+ o.items.keys
162
+ => ["RF-0001", "RF-0034", "RF-3341"]
163
+ o.items["RF-0034"].descr
164
+ => "Chocolate"
165
+ o.items["RF-0034"].total_price
166
+ => 142.5
167
+ o.signatures
168
+ => [#<Signature:0x007fb015493b68 @position="product manager", @name="John Doe">, #<Signature:0x007fb015492c68 @position="clerk", @name="Jill Smith">, #<Signature:0x007fb015491a20 @position="Some Employee", @name="Miles O'Brien">]
169
+ o.signatures[2].name
170
+ => "Miles O'Brien"
171
+ o.signatures[2].position
172
+ => "Some Employee"
173
+ ## default value was set
174
+
175
+ o.total_price
176
+ => 257.5
177
+
178
+ ####write access
179
+ o.client.name="James T. Kirk"
180
+ o.items['RF-4711'] = Item.new
181
+ o.items['RF-4711'].descr = 'power transfer grid'
182
+ o.items['RF-4711'].quantity = 2
183
+ o.items['RF-4711'].unit_price = 29.95
184
+
185
+ s=Signature.new
186
+ s.name='Harry Smith'
187
+ s.position='general manager'
188
+ o.signatures << s
189
+ xml=o.save_to_xml #convert to REXML node; there's also o.save_to_file(name)
190
+ => <order reference='12343-AHSHE-314159'> ... </>
191
+ xml.write($stdout,2)
192
+ <order reference='12343-AHSHE-314159'>
193
+ <Client>
194
+ <Name>
195
+ James T. Kirk
196
+ </Name>
197
+ <Address where='home'>
198
+ <City>
199
+ San Mateo
200
+ </City>
201
+ <State>
202
+ CA
203
+ </State>
204
+ <ZIP>
205
+ 94403
206
+ </ZIP>
207
+ <Street>
208
+ 2000, Alameda de las Pulgas
209
+ </Street>
210
+ </Address>
211
+ <Address where='work'>
212
+ <City>
213
+ San Francisco
214
+ </City>
215
+ <State>
216
+ CA
217
+ </State>
218
+ <ZIP>
219
+ 94102
220
+ </ZIP>
221
+ <Street>
222
+ 98765, Fulton Street
223
+ </Street>
224
+ </Address>
225
+ </Client>
226
+ <Item reference='RF-0001'>
227
+ <Description>
228
+ Stuffed Penguin
229
+ </Description>
230
+ <Quantity>
231
+ 10
232
+ </Quantity>
233
+ <UnitPrice>
234
+ 8.95
235
+ </UnitPrice>
236
+ </Item>
237
+ <Item reference='RF-0034'>
238
+ <Description>
239
+ Chocolate
240
+ </Description>
241
+ <Quantity>
242
+ 5
243
+ </Quantity>
244
+ <UnitPrice>
245
+ 28.5
246
+ </UnitPrice>
247
+ </Item>
248
+ <Item reference='RF-3341'>
249
+ <Description>
250
+ Cookie
251
+ </Description>
252
+ <Quantity>
253
+ 30
254
+ </Quantity>
255
+ <UnitPrice>
256
+ 0.85
257
+ </UnitPrice>
258
+ </Item>
259
+ <Item reference='RF-4711'>
260
+ <Description>
261
+ power transfer grid
262
+ </Description>
263
+ <Quantity>
264
+ 2
265
+ </Quantity>
266
+ <UnitPrice>
267
+ 29.95
268
+ </UnitPrice>
269
+ </Item>
270
+ <Signed-By>
271
+ <Signature>
272
+ <Name>
273
+ John Doe
274
+ </Name>
275
+ <Position>
276
+ product manager
277
+ </Position>
278
+ </Signature>
279
+ <Signature>
280
+ <Name>
281
+ Jill Smith
282
+ </Name>
283
+ <Position>
284
+ clerk
285
+ </Position>
286
+ </Signature>
287
+ <Signature>
288
+ <Name>
289
+ Miles O&apos;Brien
290
+ </Name>
291
+ </Signature>
292
+ <Signature>
293
+ <Name>
294
+ Harry Smith
295
+ </Name>
296
+ <Position>
297
+ general manager
298
+ </Position>
299
+ </Signature>
300
+ </Signed-By>
301
+ </order>
302
+
303
+
304
+ ####Starting a new order from scratch
305
+ o = Order.new
306
+ => #<Order:0x007fb0153bfef8 @signatures=[]>
307
+ ## attributes with default values (here: signatures) are set
308
+ ## automatically
309
+
310
+ xml=o.save_to_xml
311
+ XML::MappingError: no value, and no default value, for attribute: reference
312
+ from /home/olaf/xml-mapping/lib/xml/mapping/base.rb:712:in `obj_to_xml'
313
+ from /home/olaf/xml-mapping/lib/xml/mapping/base.rb:218:in `block in fill_into_xml'
314
+ from /home/olaf/xml-mapping/lib/xml/mapping/base.rb:217:in `each'
315
+ from /home/olaf/xml-mapping/lib/xml/mapping/base.rb:217:in `fill_into_xml'
316
+ from /home/olaf/xml-mapping/lib/xml/mapping/base.rb:229:in `save_to_xml'
317
+ ## can't save as long as there are still unset attributes without
318
+ ## default values
319
+
320
+ o.reference = "FOOBAR-1234"
321
+
322
+ o.client = Client.new
323
+ o.client.name = 'Ford Prefect'
324
+ o.client.home_address = Address.new
325
+ o.client.home_address.street = '42 Park Av.'
326
+ o.client.home_address.city = 'small planet'
327
+ o.client.home_address.zip = 17263
328
+ o.client.home_address.state = 'Betelgeuse system'
329
+
330
+ o.items={'XY-42' => Item.new}
331
+ o.items['XY-42'].descr = 'improbability drive'
332
+ o.items['XY-42'].quantity = 3
333
+ o.items['XY-42'].unit_price = 299.95
334
+
335
+ xml=o.save_to_xml
336
+ xml.write($stdout,2)
337
+
338
+ <order reference='FOOBAR-1234'>
339
+ <Client>
340
+ <Name>
341
+ Ford Prefect
342
+ </Name>
343
+ <Address where='home'>
344
+ <City>
345
+ small planet
346
+ </City>
347
+ <State>
348
+ Betelgeuse system
349
+ </State>
350
+ <ZIP>
351
+ 17263
352
+ </ZIP>
353
+ <Street>
354
+ 42 Park Av.
355
+ </Street>
356
+ </Address>
357
+ </Client>
358
+ <Item reference='XY-42'>
359
+ <Description>
360
+ improbability drive
361
+ </Description>
362
+ <Quantity>
363
+ 3
364
+ </Quantity>
365
+ <UnitPrice>
366
+ 299.95
367
+ </UnitPrice>
368
+ </Item>
369
+ </order>
370
+ ## the root element name when saving an object to XML will by default
371
+ ## be derived from the class name (in this example, "Order" became
372
+ ## "order"). This can be overridden on a per-class basis; see
373
+ ## XML::Mapping::ClassMethods#root_element_name for details.
374
+
42
375
 
43
376
  As shown in the example, you have to include XML::Mapping into a class
44
377
  to turn it into a "mapping class". There are no other restrictions
@@ -55,18 +388,18 @@ from the body of the class definition to define instance attributes
55
388
  that are automatically and bidirectionally mapped to subtrees of the
56
389
  XML element an instance of the class is mapped to.
57
390
 
58
- == {Single-attribute Nodes}[a:sanodes]
391
+ ## Single-attribute Nodes
59
392
 
60
393
  For example, in the definition
61
394
 
62
- class Address
63
- include XML::Mapping
395
+ class Address
396
+ include XML::Mapping
64
397
 
65
- text_node :city, "City"
66
- text_node :state, "State"
67
- numeric_node :zip, "ZIP"
68
- text_node :street, "Street"
69
- end
398
+ text_node :city, "City"
399
+ text_node :state, "State"
400
+ numeric_node :zip, "ZIP"
401
+ text_node :street, "Street"
402
+ end
70
403
 
71
404
  the first call to #text_node creates an attribute named "city" which
72
405
  is mapped to the text of the XML child element defined by the XPath
@@ -90,7 +423,7 @@ node". All node types that come with xml-mapping except one
90
423
  nodes.
91
424
 
92
425
 
93
- === {Default Values}[a:defaultvalues]
426
+ ### Default Values
94
427
 
95
428
  For each single-attribute node you may define a <i>default value</i>
96
429
  which will be set if there was no value defined for the attribute in
@@ -98,43 +431,43 @@ the XML source.
98
431
 
99
432
  From the example:
100
433
 
101
- class Signature
102
- include XML::Mapping
434
+ class Signature
435
+ include XML::Mapping
103
436
 
104
- text_node :position, "Position", :default_value=>"Some Employee"
105
- end
437
+ text_node :position, "Position", :default_value=>"Some Employee"
438
+ end
106
439
 
107
440
  The semantics of default values are as follows:
108
441
 
109
442
  - when creating a new instance from scratch:
110
443
 
111
- - attributes with default values are set to their default values
444
+ - attributes with default values are set to their default values
112
445
 
113
- - attributes without default values are left unset
446
+ - attributes without default values are left unset
114
447
 
115
448
  (when defining your own initializer, you'll have to call the
116
449
  inherited _initialize_ method in order to get this behaviour)
117
450
 
118
451
  - when loading an instance from an XML document:
119
452
 
120
- - attributes without default values that are not represented in the
121
- XML raise an error
453
+ - attributes without default values that are not represented in
454
+ the XML raise an error
122
455
 
123
- - attributes with default values that are not represented in the XML
124
- are set to their default values
456
+ - attributes with default values that are not represented in the
457
+ XML are set to their default values
125
458
 
126
- - all other attributes are set to their respective values as present
127
- in the XML
459
+ - all other attributes are set to their respective values as
460
+ present in the XML
128
461
 
129
462
 
130
463
  - when saving an instance to an XML document:
131
464
 
132
- - unset attributes without default values raise an error
465
+ - unset attributes without default values raise an error
133
466
 
134
- - attributes with default values that are set to their default
135
- values are not saved
467
+ - attributes with default values that are set to their default
468
+ values are not saved
136
469
 
137
- - all other attributes are saved
470
+ - all other attributes are saved
138
471
 
139
472
 
140
473
  This implies that:
@@ -147,14 +480,14 @@ This implies that:
147
480
 
148
481
 
149
482
 
150
- === {Single-attribute Nodes with Sub-objects}[a:subobjnodes]
483
+ ### Single-attribute Nodes with Sub-objects
151
484
 
152
485
  Single-attribute nodes of type +array_node+, +hash_node+, and
153
486
  +object_node+ recursively map one or more subtrees of their XML to
154
487
  sub-objects (e.g. array elements or hash values) of their
155
488
  attribute. For example, with the line
156
489
 
157
- array_node :signatures, "Signed-By", "Signature", :class=>Signature, :default_value=>[]
490
+ array_node :signatures, "Signed-By", "Signature", :class=>Signature, :default_value=>[]
158
491
 
159
492
  , an attribute named "signatures" is added to the surrounding class
160
493
  (here: +Order+); the attribute will be an array whose elements
@@ -174,17 +507,17 @@ attribute contains an array with 3 +Signature+ instances (let's call
174
507
  them <tt>sig1</tt>, <tt>sig2</tt>, and <tt>sig3</tt>) in it, it will
175
508
  be marshalled to an XML tree that looks like this:
176
509
 
177
- <Signed-By>
178
- <Signature>
179
- [marshalled object sig1]
180
- </Signature>
181
- <Signature>
182
- [marshalled object sig2]
183
- </Signature>
184
- <Signature>
185
- [marshalled object sig3]
186
- </Signature>
187
- </Signed-By>
510
+ <Signed-By>
511
+ <Signature>
512
+ [marshalled object sig1]
513
+ </Signature>
514
+ <Signature>
515
+ [marshalled object sig2]
516
+ </Signature>
517
+ <Signature>
518
+ [marshalled object sig3]
519
+ </Signature>
520
+ </Signed-By>
188
521
 
189
522
  Internally, each +Signature+ instance is stored into its
190
523
  <tt><Signature></tt> sub-element by calling
@@ -212,16 +545,59 @@ strings or numbers as sub-objects of attributes of +array_node+,
212
545
  +hash_node+, or +object_node+ nodes. For example, say you have an XML
213
546
  document like this one:
214
547
 
215
- :include: stringarray.xml
548
+ <?xml version="1.0" encoding="ISO-8859-1"?>
549
+
550
+ <people>
551
+ <names>
552
+ <name>Jim</name>
553
+ <name>Susan</name>
554
+ <name>Herbie</name>
555
+ <name>Nancy</name>
556
+ </names>
557
+ </people>
216
558
 
217
559
  , and you want to map all the names to a string array attribute
218
560
  +names+, you could do it like this:
219
561
 
220
- :include: stringarray.rb
562
+ require 'xml/mapping'
563
+ class People
564
+ include XML::Mapping
565
+ array_node :names, "names", "name", :class=>String
566
+ end
221
567
 
222
568
  usage:
223
569
 
224
- :include: stringarray_usage.intout
570
+ ppl=People.load_from_file("stringarray.xml")
571
+ => #<People:0x007fb015431a58 @names=["Jim", "Susan", "Herbie", "Nancy"]>
572
+ ppl.names
573
+ => ["Jim", "Susan", "Herbie", "Nancy"]
574
+
575
+ ppl.names.concat ["Mary","Arnold"]
576
+ => ["Jim", "Susan", "Herbie", "Nancy", "Mary", "Arnold"]
577
+ ppl.save_to_xml.write $stdout,2
578
+
579
+ <people>
580
+ <names>
581
+ <name>
582
+ Jim
583
+ </name>
584
+ <name>
585
+ Susan
586
+ </name>
587
+ <name>
588
+ Herbie
589
+ </name>
590
+ <name>
591
+ Nancy
592
+ </name>
593
+ <name>
594
+ Mary
595
+ </name>
596
+ <name>
597
+ Arnold
598
+ </name>
599
+ </names>
600
+ </people>
225
601
 
226
602
  As a side node, this feature actually makes +text_node+ and
227
603
  +numeric_node+ special cases of +object_node+. For example,
@@ -229,7 +605,7 @@ As a side node, this feature actually makes +text_node+ and
229
605
  "path", :class=>String</tt>.
230
606
 
231
607
 
232
- ==== Polymorphic Sub-objects, Marshallers/Unmarshallers
608
+ #### Polymorphic Sub-objects, Marshallers/Unmarshallers
233
609
 
234
610
  Besides the <tt>:class</tt> keyword argument, there are alternative
235
611
  ways for a single-attribute node with sub-objects to specify the way
@@ -244,15 +620,105 @@ recursive set of named "documents" and "folders", where folders hold a
244
620
  set of entries, each of which may again be either a document or a
245
621
  folder:
246
622
 
247
- :include: documents_folders.xml
623
+ <?xml version="1.0" encoding="ISO-8859-1"?>
624
+
625
+ <folder name="home">
626
+ <document name="plan">
627
+ <contents> inhale, exhale</contents>
628
+ </document>
629
+
630
+ <folder name="work">
631
+ <folder name="xml-mapping">
632
+ <document name="README">
633
+ <contents>foo bar baz</contents>
634
+ </document>
635
+ </folder>
636
+ </folder>
637
+
638
+ </folder>
248
639
 
249
640
  This can be mapped to Ruby like this:
250
641
 
251
- :include: documents_folders.rb
642
+ require 'xml/mapping'
643
+
644
+ class Entry
645
+ include XML::Mapping
646
+
647
+ text_node :name, "@name"
648
+ end
649
+
650
+
651
+ class Document <Entry
652
+ include XML::Mapping
653
+
654
+ text_node :contents, "contents"
655
+ end
656
+
657
+
658
+ class Folder <Entry
659
+ include XML::Mapping
660
+
661
+ array_node :entries, "document|folder", :default_value=>[]
662
+
663
+ def [](name)
664
+ entries.select{|e|e.name==name}[0]
665
+ end
666
+
667
+ def append(name,entry)
668
+ entries << entry
669
+ entry.name = name
670
+ entry
671
+ end
672
+ end
252
673
 
253
674
  Usage:
254
675
 
255
- :include: documents_folders_usage.intout
676
+
677
+ root = XML::Mapping.load_object_from_file "documents_folders.xml"
678
+ => #<Folder:0x007fb015437098 @entries=[#<Document:0x007fb015436148 @name="plan", @contents=" inhale, exhale">, #<Folder:0x007fb0154352c0 @entries=[#<Folder:0x007fb015434550 @entries=[#<Document:0x007fb015432e30 @name="README", @contents="foo bar baz">], @name="xml-mapping">], @name="work">], @name="home">
679
+ root.name
680
+ => "home"
681
+ root.entries
682
+ => [#<Document:0x007fb015436148 @name="plan", @contents=" inhale, exhale">, #<Folder:0x007fb0154352c0 @entries=[#<Folder:0x007fb015434550 @entries=[#<Document:0x007fb015432e30 @name="README", @contents="foo bar baz">], @name="xml-mapping">], @name="work">]
683
+
684
+ root.append "etc", Folder.new
685
+ root["etc"].append "passwd", Document.new
686
+ root["etc"]["passwd"].contents = "foo:x:2:2:/bin/sh"
687
+ root["etc"].append "hosts", Document.new
688
+ root["etc"]["hosts"].contents = "127.0.0.1 localhost"
689
+
690
+ xml = root.save_to_xml
691
+ => <folder name='home'> ... </>
692
+ xml.write $stdout,2
693
+
694
+ <folder name='home'>
695
+ <document name='plan'>
696
+ <contents>
697
+ inhale, exhale
698
+ </contents>
699
+ </document>
700
+ <folder name='work'>
701
+ <folder name='xml-mapping'>
702
+ <document name='README'>
703
+ <contents>
704
+ foo bar baz
705
+ </contents>
706
+ </document>
707
+ </folder>
708
+ </folder>
709
+ <folder name='etc'>
710
+ <document name='passwd'>
711
+ <contents>
712
+ foo:x:2:2:/bin/sh
713
+ </contents>
714
+ </document>
715
+ <document name='hosts'>
716
+ <contents>
717
+ 127.0.0.1 localhost
718
+ </contents>
719
+ </document>
720
+ </folder>
721
+ </folder>
256
722
 
257
723
  As you see, the <tt>Folder#entries</tt> attribute is mapped via an
258
724
  array_node that does not specify a <tt>:class</tt> or anything else to
@@ -297,7 +763,15 @@ extend the +Signature+ class from the initial example to include the
297
763
  date on which the signature was created. We want the new XML
298
764
  representation of such a signature to look like this:
299
765
 
300
- :include: time_node_w_marshallers.xml
766
+ <Signature>
767
+ <Name>John Doe</Name>
768
+ <Position>product manager</Position>
769
+ <signed-on>
770
+ <day>13</day>
771
+ <month>2</month>
772
+ <year>2005</year>
773
+ </signed-on>
774
+ </Signature>
301
775
 
302
776
  So, a new "signed-on" element was added that holds the day, month, and
303
777
  year. In the +Signature+ instance in Ruby, we want the date to be
@@ -313,7 +787,31 @@ here[aref:definingnodes]). The fastest, most ad-hoc way to achieve
313
787
  what we want are :marshaller and :unmarshaller keyword arguments, like
314
788
  this:
315
789
 
316
- :include: time_node_w_marshallers.intout
790
+ require 'xml/mapping'
791
+ require 'xml/xxpath_methods'
792
+
793
+ class Signature
794
+ include XML::Mapping
795
+
796
+ text_node :name, "Name"
797
+ text_node :position, "Position", :default_value=>"Some Employee"
798
+ object_node :signed_on, "signed-on",
799
+ :unmarshaller=>proc{|xml|
800
+ y,m,d = [xml.first_xpath("year").text.to_i,
801
+ xml.first_xpath("month").text.to_i,
802
+ xml.first_xpath("day").text.to_i]
803
+ Time.local(y,m,d)
804
+ },
805
+ :marshaller=>proc{|xml,value|
806
+ e = xml.elements.add; e.name = "year"; e.text = value.year
807
+ e = xml.elements.add; e.name = "month"; e.text = value.month
808
+ e = xml.elements.add; e.name = "day"; e.text = value.day
809
+
810
+ # xml.first("year",:ensure_created=>true).text = value.year
811
+ # xml.first("month",:ensure_created=>true).text = value.month
812
+ # xml.first("day",:ensure_created=>true).text = value.day
813
+ }
814
+ end
317
815
 
318
816
  The <tt>:unmarshaller</tt> proc will be called whenever a +Signature+
319
817
  instance is being read in from an XML source. The +xml+ argument
@@ -353,9 +851,9 @@ possible with all single-attribute nodes with sub-objects, i.e. with
353
851
  a whole array of date values, you could use +array_node+ with the same
354
852
  :marshaller/:unmarshaller procs as above, for example:
355
853
 
356
- array_node :birthdays, "birthdays", "birthday",
357
- :unmarshaller=> <as above>,
358
- :marshaller=> <as above>
854
+ array_node :birthdays, "birthdays", "birthday",
855
+ :unmarshaller=> <as above>,
856
+ :marshaller=> <as above>
359
857
 
360
858
  You can see that :marshaller/:unmarshaller procs give you more
361
859
  flexibility, but they also impose more work because you essentially
@@ -383,12 +881,12 @@ functionality of a node type while retaining another part.
383
881
 
384
882
 
385
883
 
386
- === {Attribute Handling Details, Augmenting Existing Classes}[a:attrdefns]
884
+ ### Attribute Handling Details, Augmenting Existing Classes
387
885
 
388
886
  I'll shed some more light on how single-attribute nodes add mapped
389
887
  attributes to Ruby classes. An attribute declaration like
390
888
 
391
- text_node :city, "City"
889
+ text_node :city, "City"
392
890
 
393
891
  maps some portion of the XML tree (here: the "City" sub-element) to an
394
892
  attribute (here: "city") of the class whose body the declaration
@@ -418,7 +916,41 @@ scratch, you can also take existing classes that contain some
418
916
  a simple example, let's augment Ruby's "Time" class with node
419
917
  declarations that declare XML mappings for the day, month etc. fields:
420
918
 
421
- :include: time_augm.intout
919
+ class Time
920
+ include XML::Mapping
921
+
922
+ numeric_node :year, "year"
923
+ numeric_node :month, "month"
924
+ numeric_node :day, "mday"
925
+ numeric_node :hour, "hours"
926
+ numeric_node :min, "minutes"
927
+ numeric_node :sec, "seconds"
928
+ end
929
+
930
+
931
+ nowxml=Time.now.save_to_xml
932
+ => <time> ... </>
933
+ nowxml.write($stdout,2)
934
+ <time>
935
+ <year>
936
+ 2014
937
+ </year>
938
+ <month>
939
+ 9
940
+ </month>
941
+ <mday>
942
+ 19
943
+ </mday>
944
+ <hours>
945
+ 14
946
+ </hours>
947
+ <minutes>
948
+ 1
949
+ </minutes>
950
+ <seconds>
951
+ 43
952
+ </seconds>
953
+ </time>
422
954
 
423
955
  Here XML mappings are defined for the existing fields +year+, +month+
424
956
  etc. Xml-mapping noticed that the getter methods for those attributes
@@ -445,10 +977,21 @@ variables, so the setter methods don't really change the time of the
445
977
  +Time+ object. So we have to redefine +load_from_xml+ for the +Time+
446
978
  class:
447
979
 
448
- :include: time_augm_loading.intout
980
+
981
+ def Time.load_from_xml(xml, options={:mapping=>:_default})
982
+ year,month,day,hour,min,sec =
983
+ [xml.first_xpath("year").text.to_i,
984
+ xml.first_xpath("month").text.to_i,
985
+ xml.first_xpath("mday").text.to_i,
986
+ xml.first_xpath("hours").text.to_i,
987
+ xml.first_xpath("minutes").text.to_i,
988
+ xml.first_xpath("seconds").text.to_i]
989
+ Time.local(year,month,day,hour,min,sec)
990
+ end
991
+
449
992
 
450
993
 
451
- == {Other Nodes}[a:onodes]
994
+ ## Other Nodes
452
995
 
453
996
  All nodes I've shown so far (node types text_node, numeric_node,
454
997
  boolean_node, object_node, array_node, and hash_node) were
@@ -457,7 +1000,7 @@ of such a node is an attribute name, and the attribute of that name is
457
1000
  the only piece of the state of instances of the node's mapping class
458
1001
  that gets read/written by the node.
459
1002
 
460
- === {choice_node}[a:choice_node]
1003
+ ### choice_node
461
1004
 
462
1005
  There is one node type distributed with xml-mapping that is not a
463
1006
  single-attribute node: +choice_node+. A +choice_node+ allows you to
@@ -477,7 +1020,26 @@ objects that have either a single author (contained in an "author" XML
477
1020
  attribute) or several "contributors" (contained in a sequence of
478
1021
  "contr" XML elements):
479
1022
 
480
- :include: publication.intout
1023
+
1024
+ class Publication
1025
+ include XML::Mapping
1026
+
1027
+ choice_node :if, '@author', :then, (text_node :author, '@author'),
1028
+ :elsif, 'contr', :then, (array_node :contributors, 'contr', :class=>String)
1029
+ end
1030
+
1031
+ ### usage
1032
+
1033
+ p1 = Publication.load_from_xml(REXML::Document.new('<publication author="Jim"/>').root)
1034
+ => #<Publication:0x007fb01548e668 @author="Jim">
1035
+
1036
+ p2 = Publication.load_from_xml(REXML::Document.new('
1037
+ <publication>
1038
+ <contr>Chris</contr>
1039
+ <contr>Mel</contr>
1040
+ <contr>Toby</contr>
1041
+ </publication>').root)
1042
+ => #<Publication:0x007fb01547ef38 @contributors=["Chris", "Mel", "Toby"]>
481
1043
 
482
1044
  The symbols :if, :then, and :elsif (but not :else -- see below) in the
483
1045
  +choice_node+'s node factory method call are ignored; they may be
@@ -519,7 +1081,33 @@ person should be stored alternatively in a sub-element named +name+,
519
1081
  or an attribute named +name+, or in the text of the +person+ element
520
1082
  itself. You can achieve this with +choice_node+ like this:
521
1083
 
522
- :include: person.intout
1084
+
1085
+ class Person
1086
+ include XML::Mapping
1087
+
1088
+ choice_node :if, 'name', :then, (text_node :name, 'name'),
1089
+ :elsif, '@name', :then, (text_node :name, '@name'),
1090
+ :else, (text_node :name, '.')
1091
+ end
1092
+
1093
+ ### usage
1094
+
1095
+ p1 = Person.load_from_xml(REXML::Document.new('<person name="Jim"/>').root)
1096
+ => #<Person:0x007fb014e95298 @name="Jim">
1097
+
1098
+ p2 = Person.load_from_xml(REXML::Document.new('<person><name>James</name></person>').root)
1099
+ => #<Person:0x007fb014e847b8 @name="James">
1100
+
1101
+ p3 = Person.load_from_xml(REXML::Document.new('<person>Suzy</person>').root)
1102
+ => #<Person:0x007fb014e6c550 @name="Suzy">
1103
+
1104
+
1105
+ p1.save_to_xml.write($stdout)
1106
+ <person><name>Jim</name></person>
1107
+ p2.save_to_xml.write($stdout)
1108
+ <person><name>James</name></person>
1109
+ p3.save_to_xml.write($stdout)
1110
+ <person><name>Suzy</name></person>
523
1111
 
524
1112
  Here all sub-nodes of the choice_nodes are single-attribute nodes
525
1113
  (text_nodes) with the same attribute (+name+). As you see, when
@@ -528,7 +1116,7 @@ sub-element. Of course, this is because that alternative appears first
528
1116
  in the choice_node.
529
1117
 
530
1118
 
531
- === {Readers/Writers}[a:readerswriters]
1119
+ ### Readers/Writers
532
1120
 
533
1121
  Finally, _all_ nodes support keyword arguments :reader and :writer
534
1122
  which allow you to extend or completely override the reading and/or
@@ -545,7 +1133,25 @@ The :reader proc is for reading (from the XML into the object), the
545
1133
 
546
1134
  Here's a (really contrived) example:
547
1135
 
548
- :include: reader.intout
1136
+
1137
+ class Foo
1138
+ include XML::Mapping
1139
+
1140
+ text_node :name, "@name", :reader=>proc{|obj,xml,default_reader|
1141
+ default_reader.call(obj,xml)
1142
+ obj.name += xml.attributes['more']
1143
+ },
1144
+ :writer=>proc{|obj,xml|
1145
+ xml.attributes['bar'] = "hi #{obj.name} ho"
1146
+ }
1147
+ end
1148
+
1149
+ f = Foo.load_from_xml(REXML::Document.new('<foo name="Jim" more="XYZ"/>').root)
1150
+ => #<Foo:0x007fb015460c40 @name="JimXYZ">
1151
+
1152
+ xml = f.save_to_xml
1153
+ xml.write $stdout,2
1154
+ <foo bar='hi JimXYZ ho'/>
549
1155
 
550
1156
  So there's a "Foo" class with a text_node that would by default
551
1157
  (without the :reader and :writer proc) map the Ruby attribute "name"
@@ -573,14 +1179,14 @@ As a special convention, if you specify both a :reader and a :writer
573
1179
  for a node, and in both cases you do /not/ call the default behaviour,
574
1180
  then you should use the generic node type +node+, e.g.:
575
1181
 
576
- class SomeClass
577
- include XML::Mapping
1182
+ class SomeClass
1183
+ include XML::Mapping
578
1184
 
579
- ....
1185
+ ....
580
1186
 
581
- node :reader=>proc{|obj,xml| ...},
582
- :writer=>proc{|obj,xml| ...}
583
- end
1187
+ node :reader=>proc{|obj,xml| ...},
1188
+ :writer=>proc{|obj,xml| ...}
1189
+ end
584
1190
 
585
1191
  (since you're completely replacing both the reading and the writing
586
1192
  functionality, you're effectively replacing all the functionality of
@@ -608,7 +1214,7 @@ the (sensibly parameterized) code from your readers/writers to your
608
1214
  node types.
609
1215
 
610
1216
 
611
- == {Multiple Mappings per Class}[a:mappings]
1217
+ ## Multiple Mappings per Class
612
1218
 
613
1219
  Sometimes you might want to represent the same Ruby object in multiple
614
1220
  alternative ways in XML. For example, the name of a "Person" object
@@ -635,7 +1241,120 @@ In the following example, we define two mappings (the default one and
635
1241
  a mapping named <tt>:other</tt>) for +Person+ objects with a name, an
636
1242
  age and an address:
637
1243
 
638
- :include: examples/person_mm.intout
1244
+ require 'xml/mapping'
1245
+
1246
+ class Address; end
1247
+
1248
+ class Person
1249
+ include XML::Mapping
1250
+
1251
+ # the default mapping. Stores the name and age in XML attributes,
1252
+ # and the address in a sub-element "address".
1253
+
1254
+ text_node :name, "@name"
1255
+ numeric_node :age, "@age"
1256
+ object_node :address, "address", :class=>Address
1257
+
1258
+ use_mapping :other
1259
+
1260
+ # the ":other" mapping. Non-default root element name; name and age
1261
+ # stored in XML elements; address stored in the person's element
1262
+ # itself
1263
+
1264
+ root_element_name "individual"
1265
+ text_node :name, "name"
1266
+ numeric_node :age, "age"
1267
+ object_node :address, ".", :class=>Address
1268
+
1269
+ # you could also specify the mapping on a per-node basis with the
1270
+ # :mapping option, e.g.:
1271
+ #
1272
+ # numeric_node :age, "age", :mapping=>:other
1273
+ end
1274
+
1275
+
1276
+ class Address
1277
+ include XML::Mapping
1278
+
1279
+ # the default mapping.
1280
+
1281
+ text_node :street, "street"
1282
+ numeric_node :number, "number"
1283
+ text_node :city, "city"
1284
+ numeric_node :zip, "zip"
1285
+
1286
+ use_mapping :other
1287
+
1288
+ # the ":other" mapping.
1289
+
1290
+ text_node :street, "street-name"
1291
+ numeric_node :number, "street-name/@number"
1292
+ text_node :city, "city-name"
1293
+ numeric_node :zip, "city-name/@zip-code"
1294
+ end
1295
+
1296
+
1297
+ ### usage
1298
+
1299
+ ## XML representation of a person in the default mapping
1300
+ xml = REXML::Document.new('
1301
+ <person name="Suzy" age="28">
1302
+ <address>
1303
+ <street>Abbey Road</street>
1304
+ <number>72</number>
1305
+ <city>London</city>
1306
+ <zip>18827</zip>
1307
+ </address>
1308
+ </person>').root
1309
+
1310
+ ## load using the default mapping
1311
+ p = Person.load_from_xml xml
1312
+ => #<Person:0x007fb015506640 @name="Suzy", @age=28, @address=#<Address:0x007fb015505a38 @street="Abbey Road", @number=72, @city="London", @zip=18827>>
1313
+
1314
+ ## save using the default mapping
1315
+ xml2 = p.save_to_xml
1316
+ xml2.write $stdout,2
1317
+ <person name='Suzy' age='28'>
1318
+ <address>
1319
+ <street>
1320
+ Abbey Road
1321
+ </street>
1322
+ <number>
1323
+ 72
1324
+ </number>
1325
+ <city>
1326
+ London
1327
+ </city>
1328
+ <zip>
1329
+ 18827
1330
+ </zip>
1331
+ </address>
1332
+ </person>
1333
+ ## xml2 identical to xml
1334
+
1335
+
1336
+ ## now, save the same person to XML using the :other mapping...
1337
+ other_xml = p.save_to_xml :mapping=>:other
1338
+ other_xml.write $stdout,2
1339
+ <individual>
1340
+ <name>
1341
+ Suzy
1342
+ </name>
1343
+ <age>
1344
+ 28
1345
+ </age>
1346
+ <street-name number='72'>
1347
+ Abbey Road
1348
+ </street-name>
1349
+ <city-name zip-code='18827'>
1350
+ London
1351
+ </city-name>
1352
+ </individual>
1353
+ ## load it again using the :other mapping
1354
+ p2 = Person.load_from_xml other_xml, :mapping=>:other
1355
+ => #<Person:0x007fb0154bad30 @name="Suzy", @age=28, @address=#<Address:0x007fb0154b98e0 @street="Abbey Road", @number=72, @city="London", @zip=18827>>
1356
+
1357
+ ## p2 identical to p
639
1358
 
640
1359
  In this example, each of the two mappings contains nodes that map the
641
1360
  same set of Ruby attributes (name, age and address). This is probably
@@ -652,11 +1371,11 @@ sub-ordinated class (+Address+). This is the case for all
652
1371
  specify a different mapping for the sub-object(s) using the option
653
1372
  :sub_mapping, e.g.
654
1373
 
655
- object_node :address, "address", :class=>Address, :sub_mapping=>:other
1374
+ object_node :address, "address", :class=>Address, :sub_mapping=>:other
656
1375
 
657
1376
 
658
1377
 
659
- == {Defining your own Node Types}[a:definingnodes]
1378
+ ## Defining your own Node Types
660
1379
 
661
1380
  It's easy to write additional node types and register them with the
662
1381
  xml-mapping library (the following node types come with xml-mapping:
@@ -665,18 +1384,32 @@ xml-mapping library (the following node types come with xml-mapping:
665
1384
 
666
1385
  I'll first show an example, then some more theoretical insight.
667
1386
 
668
- === Example
1387
+ ### Example
669
1388
 
670
1389
  Let's say we want to extend the +Signature+ class from the example to
671
1390
  include the time at which the signature was created. We want the new
672
1391
  XML representation of such a signature to look like this:
673
1392
 
674
- :include: order_signature_enhanced.xml
1393
+ <Signature>
1394
+ <Name>John Doe</Name>
1395
+ <Position>product manager</Position>
1396
+ <signed-on>
1397
+ <day>13</day>
1398
+ <month>2</month>
1399
+ <year>2005</year>
1400
+ </signed-on>
1401
+ </Signature>
675
1402
 
676
1403
  (we only save year, month and day to make this example shorter), and
677
1404
  the mapping class declaration to look like this:
678
1405
 
679
- :include: order_signature_enhanced.rb
1406
+ class Signature
1407
+ include XML::Mapping
1408
+
1409
+ text_node :name, "Name"
1410
+ text_node :position, "Position", :default_value=>"Some Employee"
1411
+ time_node :signed_on, "signed-on", :default_value=>Time.now
1412
+ end
680
1413
 
681
1414
  (i.e. a new "time_node" declaration was added).
682
1415
 
@@ -686,7 +1419,34 @@ class +Time+.
686
1419
 
687
1420
  This node type can be defined with this piece of code:
688
1421
 
689
- :include: time_node.rb
1422
+ require 'xml/mapping/base'
1423
+
1424
+ class TimeNode < XML::Mapping::SingleAttributeNode
1425
+ def initialize(*args)
1426
+ path,*args = super(*args)
1427
+ @y_path = XML::XXPath.new(path+"/year")
1428
+ @m_path = XML::XXPath.new(path+"/month")
1429
+ @d_path = XML::XXPath.new(path+"/day")
1430
+ args
1431
+ end
1432
+
1433
+ def extract_attr_value(xml)
1434
+ y,m,d = default_when_xpath_err{ [@y_path.first(xml).text.to_i,
1435
+ @m_path.first(xml).text.to_i,
1436
+ @d_path.first(xml).text.to_i]
1437
+ }
1438
+ Time.local(y,m,d)
1439
+ end
1440
+
1441
+ def set_attr_value(xml, value)
1442
+ @y_path.first(xml,:ensure_created=>true).text = value.year
1443
+ @m_path.first(xml,:ensure_created=>true).text = value.month
1444
+ @d_path.first(xml,:ensure_created=>true).text = value.day
1445
+ end
1446
+ end
1447
+
1448
+
1449
+ XML::Mapping.add_node_class TimeNode
690
1450
 
691
1451
  The last line registers the new node type with the xml-mapping
692
1452
  library. The name of the node factory method ("time_node") is
@@ -798,7 +1558,7 @@ _xml_. If it was not, it is created (preferably at the end of _xml_'s
798
1558
  list of sub-nodes), and returned. See below[aref:xpath] for a more
799
1559
  detailed documentation of the XPath interpreter.
800
1560
 
801
- === Element order in created XML documents
1561
+ ### Element order in created XML documents
802
1562
 
803
1563
  As just said, XML::XXPath, when used to create new XML nodes,
804
1564
  generally appends those nodes to the end of the list of subnodes of
@@ -821,7 +1581,7 @@ The following is a more systematic overview of the basic node
821
1581
  types. The description is self-contained, so some information from the
822
1582
  previous section will be repeated.
823
1583
 
824
- === Node Types Are Ruby Classes
1584
+ ### Node Types Are Ruby Classes
825
1585
 
826
1586
  A node type is implemented as a Ruby class derived from
827
1587
  XML::Mapping::Node or one of its subclasses.
@@ -830,16 +1590,16 @@ The following node types (node classes) come with xml-mapping (they
830
1590
  all live in the XML::Mapping namespace, which I've left out here for
831
1591
  brevity):
832
1592
 
833
- Node
834
- +-SingleAttributeNode
835
- | +-SubObjectBaseNode
836
- | | +-ObjectNode
837
- | | +-ArrayNode
838
- | | +-HashNode
839
- | +-TextNode
840
- | +-NumericNode
841
- | +-BooleanNode
842
- +-ChoiceNode
1593
+ Node
1594
+ +-SingleAttributeNode
1595
+ | +-SubObjectBaseNode
1596
+ | | +-ObjectNode
1597
+ | | +-ArrayNode
1598
+ | | +-HashNode
1599
+ | +-TextNode
1600
+ | +-NumericNode
1601
+ | +-BooleanNode
1602
+ +-ChoiceNode
843
1603
 
844
1604
  XML::Mapping::Node is the base class for all nodes,
845
1605
  XML::Mapping::SingleAttributeNode is the base class for
@@ -863,12 +1623,12 @@ built-in nodes is not very long or complicated; you may consider
863
1623
  reading it in addition to this text to gain a better understanding.
864
1624
 
865
1625
 
866
- === How Node Types Work
1626
+ ### How Node Types Work
867
1627
 
868
1628
  The xml-mapping core "operates" node types as follows:
869
1629
 
870
1630
 
871
- ==== Node Initialization
1631
+ #### Node Initialization
872
1632
 
873
1633
  As said above, when a node class is registered with xml-mapping by
874
1634
  calling <tt>XML::Mapping.add_node_class TheNodeClass</tt>, xml-mapping
@@ -881,11 +1641,11 @@ the node class. The list of parameters to _new_ will consist of <i>the
881
1641
  mapping class, followed by all arguments that were passed to the node
882
1642
  factory method</i>. For example, when you have this node declaration:
883
1643
 
884
- class MyMappingClass
885
- include XML::Mapping
1644
+ class MyMappingClass
1645
+ include XML::Mapping
886
1646
 
887
- my_node :foo, "bar", 42, :hi=>"ho"
888
- end
1647
+ my_node :foo, "bar", 42, :hi=>"ho"
1648
+ end
889
1649
 
890
1650
  , then the node factory method (+my_node+) calls
891
1651
  <tt>MyNode.new(MyMappingClass, :foo, "bar", 42, :hi=>"ho")</tt>.
@@ -898,14 +1658,14 @@ processes itself, process them, and return an array containing the
898
1658
  remaining (still unprocessed) parameters. Thus, an implementation of
899
1659
  _initialize_ follows this pattern:
900
1660
 
901
- def initialize(*args)
902
- myparam1,myparam2,...,myparamx,*args = super(*args)
1661
+ def initialize(*args)
1662
+ myparam1,myparam2,...,myparamx,*args = super(*args)
903
1663
 
904
- .... process the myparam1,myparam2,...,myparamx ....
1664
+ .... process the myparam1,myparam2,...,myparamx ....
905
1665
 
906
- # return still unprocessed args
907
- args
908
- end
1666
+ # return still unprocessed args
1667
+ args
1668
+ end
909
1669
 
910
1670
  (since the called superclass initializer is written the same way, the
911
1671
  parameter array returned by it will already be stripped of all
@@ -928,7 +1688,7 @@ per node definition, and that instance lives in the mapping class the
928
1688
  node was defined in.
929
1689
 
930
1690
 
931
- ==== Node Operation during Marshalling and Unmarshalling
1691
+ #### Node Operation during Marshalling and Unmarshalling
932
1692
 
933
1693
  When an instance of a mapping class is created or filled from an XML
934
1694
  tree, xml-mapping will call +xml_to_obj+ on all nodes defined in that
@@ -951,7 +1711,7 @@ the instance and put it into the appropriate XML elements/XML attr
951
1711
  etc. of the XML tree.
952
1712
 
953
1713
 
954
- === Basic Node Types Overview
1714
+ ### Basic Node Types Overview
955
1715
 
956
1716
  The following is an overview of how initialization and
957
1717
  marshalling/unmarshalling is implemented in the node base classes
@@ -959,7 +1719,7 @@ marshalling/unmarshalling is implemented in the node base classes
959
1719
 
960
1720
  TODO: summary table: member var name; introduced in class; meaning
961
1721
 
962
- ==== Node
1722
+ #### Node
963
1723
 
964
1724
  In _initialize_, the mapping class and the option arguments are
965
1725
  stripped from the argument list. The mapping class is stored in
@@ -990,7 +1750,7 @@ The marshalling/unmarshalling methods
990
1750
  +Node+ (they just raise an exception).
991
1751
 
992
1752
 
993
- ==== SingleAttributeNode
1753
+ #### SingleAttributeNode
994
1754
 
995
1755
  In _initialize_, the attribute name is stripped from the argument list
996
1756
  and stored in @attrname, and an attribute of that name is added to the
@@ -1003,9 +1763,9 @@ this, the <tt>obj_to_xml</tt>/<tt>xml_to_obj</tt> implementations in
1003
1763
  SingleAttributeNode call two new methods introduced by
1004
1764
  SingleAttributeNode, which must be overwritten by subclasses:
1005
1765
 
1006
- extract_attr_value(xml)
1766
+ extract_attr_value(xml)
1007
1767
 
1008
- set_attr_value(xml, value)
1768
+ set_attr_value(xml, value)
1009
1769
 
1010
1770
  <tt>extract_attr_value(xml)</tt> is called by <tt>xml_to_obj</tt>
1011
1771
  during unmarshalling. _xml_ is the XML tree being read. The method
@@ -1028,7 +1788,7 @@ XML. SingleAttributeNode will catch this exception and put the default
1028
1788
  value, if it was defined, into the attribute.
1029
1789
 
1030
1790
 
1031
- ==== SubObjectBaseNode
1791
+ #### SubObjectBaseNode
1032
1792
 
1033
1793
  The initializer will set up additional member variables @sub_mapping,
1034
1794
  @marshaller, and @unmarshaller.
@@ -1045,7 +1805,7 @@ procs are there to be called from <tt>extract_attr_value</tt> or
1045
1805
  <tt>set_attr_value</tt> whenever the need arises.
1046
1806
 
1047
1807
 
1048
- == {XPath Interpreter}[a:xpath]
1808
+ ## XPath Interpreter
1049
1809
 
1050
1810
  XML::XXPath is an XPath parser. It is used in xml-mapping node type
1051
1811
  definitions, but can just as well be utilized stand-alone (it does not
@@ -1053,32 +1813,34 @@ depend on xml-mapping). XML::XXPath is very incomplete and probably
1053
1813
  will always be, but it should be reasonably efficient (XPath
1054
1814
  expressions are precompiled), and, most importantly, it supports write
1055
1815
  access, which is needed for writing objects to XML. For example, if
1056
- you create the path "/foo/bar[3]/baz[@key='hiho']" in the XML document
1816
+ you create the path <tt>/foo/bar[3]/baz[@key='hiho']</tt> in the XML
1817
+ document
1057
1818
 
1058
- <foo>
1059
- <bar>
1060
- <baz key="ab">hello</baz>
1061
- <baz key="xy">goodbye</baz>
1062
- </bar>
1063
- </foo>
1819
+ <foo>
1820
+ <bar>
1821
+ <baz key="ab">hello</baz>
1822
+ <baz key="xy">goodbye</baz>
1823
+ </bar>
1824
+ </foo>
1064
1825
 
1065
1826
  , you'll get:
1066
1827
 
1067
- <foo>
1068
- <bar>
1069
- <baz key='ab'>hello</baz>
1070
- <baz key='xy'>goodbye</baz>
1071
- </bar>
1072
- <bar/>
1073
- <bar>
1074
- <baz key='hiho'/>
1075
- </bar>
1076
- </foo>
1828
+ <foo>
1829
+ <bar>
1830
+ <baz key='ab'>hello</baz>
1831
+ <baz key='xy'>goodbye</baz>
1832
+ </bar>
1833
+ <bar/>
1834
+ <bar>
1835
+ <baz key='hiho'/>
1836
+ </bar>
1837
+ </foo>
1077
1838
 
1078
1839
  XML::XXPath is explained in more detail in the reference documentation
1079
- and the README_XPATH file.
1840
+ and the user_manual_xxpath file.
1080
1841
 
1081
1842
 
1082
- == License
1843
+ ## License
1083
1844
 
1084
- Ruby's.
1845
+ xml-mapping is licensed under the Apache License, version 2.0. See the
1846
+ LICENSE file for details.