xml-mapping 0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. data/LICENSE +56 -0
  2. data/README +386 -0
  3. data/README_XPATH +175 -0
  4. data/Rakefile +214 -0
  5. data/TODO.txt +32 -0
  6. data/doc/xpath_impl_notes.txt +119 -0
  7. data/examples/company.rb +34 -0
  8. data/examples/company.xml +26 -0
  9. data/examples/company_usage.intin.rb +19 -0
  10. data/examples/company_usage.intout +39 -0
  11. data/examples/order.rb +61 -0
  12. data/examples/order.xml +54 -0
  13. data/examples/order_signature_enhanced.rb +7 -0
  14. data/examples/order_signature_enhanced.xml +9 -0
  15. data/examples/order_signature_enhanced_usage.intin.rb +12 -0
  16. data/examples/order_signature_enhanced_usage.intout +16 -0
  17. data/examples/order_usage.intin.rb +73 -0
  18. data/examples/order_usage.intout +147 -0
  19. data/examples/time_augm.intin.rb +19 -0
  20. data/examples/time_augm.intout +23 -0
  21. data/examples/time_node.rb +27 -0
  22. data/examples/xpath_create_new.intin.rb +85 -0
  23. data/examples/xpath_create_new.intout +181 -0
  24. data/examples/xpath_docvsroot.intin.rb +30 -0
  25. data/examples/xpath_docvsroot.intout +34 -0
  26. data/examples/xpath_ensure_created.intin.rb +62 -0
  27. data/examples/xpath_ensure_created.intout +114 -0
  28. data/examples/xpath_pathological.intin.rb +42 -0
  29. data/examples/xpath_pathological.intout +56 -0
  30. data/examples/xpath_usage.intin.rb +51 -0
  31. data/examples/xpath_usage.intout +57 -0
  32. data/install.rb +40 -0
  33. data/lib/xml/mapping.rb +14 -0
  34. data/lib/xml/mapping/base.rb +563 -0
  35. data/lib/xml/mapping/standard_nodes.rb +343 -0
  36. data/lib/xml/mapping/version.rb +8 -0
  37. data/lib/xml/xxpath.rb +354 -0
  38. data/test/all_tests.rb +6 -0
  39. data/test/company.rb +54 -0
  40. data/test/documents_folders.rb +33 -0
  41. data/test/fixtures/bookmarks1.xml +24 -0
  42. data/test/fixtures/company1.xml +85 -0
  43. data/test/fixtures/documents_folders.xml +71 -0
  44. data/test/fixtures/documents_folders2.xml +30 -0
  45. data/test/multiple_mappings.rb +80 -0
  46. data/test/tests_init.rb +2 -0
  47. data/test/xml_mapping_adv_test.rb +84 -0
  48. data/test/xml_mapping_test.rb +182 -0
  49. data/test/xpath_test.rb +273 -0
  50. metadata +96 -0
@@ -0,0 +1,214 @@
1
+ # -*- ruby -*-
2
+ # adapted from active_record's Rakefile
3
+
4
+ require 'rubygems'
5
+ require 'rake'
6
+ require 'rake/testtask'
7
+ require 'rake/rdoctask'
8
+ require 'rake/packagetask'
9
+ require 'rake/gempackagetask'
10
+ #require 'rake/contrib/rubyforgepublisher'
11
+ require 'rake/contrib/sshpublisher'
12
+
13
+ require File.dirname(__FILE__)+"/lib/xml/mapping/version"
14
+
15
+
16
+ # yeah -- it's just stupid that these are private
17
+
18
+ class Rake::RDocTask
19
+ public :rdoc_target
20
+ end
21
+
22
+ class Rake::PackageTask
23
+ public :tgz_file, :zip_file
24
+ end
25
+
26
+ class Rake::GemPackageTask
27
+ public :gem_file
28
+ end
29
+
30
+
31
+ FILES_RDOC_EXTRA=%w{README README_XPATH TODO.txt doc/xpath_impl_notes.txt}
32
+ FILES_RDOC_INCLUDES=%w{examples/company.xml
33
+ examples/company.rb
34
+ examples/company_usage.intout
35
+ examples/order_usage.intout
36
+ examples/time_augm.intout
37
+ examples/xpath_usage.intout
38
+ examples/xpath_ensure_created.intout
39
+ examples/xpath_create_new.intout
40
+ examples/xpath_pathological.intout
41
+ examples/xpath_docvsroot.intout
42
+ examples/order_signature_enhanced_usage.intout}
43
+
44
+
45
+ desc "Default Task"
46
+ task :default => [ :test ]
47
+
48
+ Rake::TestTask.new(:test) { |t|
49
+ t.test_files = ["test/all_tests.rb"]
50
+ t.verbose = true
51
+ }
52
+
53
+ # runs tests only if sources have changed since last succesful run of
54
+ # tests
55
+ file "test_run" => FileList.new('lib/**/*.rb','test/**/*.rb') do
56
+ Task[:test].invoke
57
+ touch "test_run"
58
+ end
59
+
60
+
61
+
62
+ Rake::RDocTask.new { |rdoc|
63
+ rdoc.rdoc_dir = 'doc/api'
64
+ rdoc.title = "XML::Mapping -- Simple, extensible Ruby-to-XML (and back) mapper"
65
+ rdoc.options << '--line-numbers --inline-source --accessor cattr_accessor=object --include examples'
66
+ rdoc.rdoc_files.include(*FILES_RDOC_EXTRA)
67
+ rdoc.rdoc_files.include('lib/**/*.rb')
68
+
69
+ # additional file dependencies for the rdoc task
70
+ # this somewhat of a black art because RDocTask doesn't document the
71
+ # prerequisite of its rdoc task (<rdoc_dir>/index.html)
72
+ file rdoc.rdoc_target => FILES_RDOC_INCLUDES
73
+ file "#{rdoc.rdoc_dir}/index.html" => FileList.new("examples/**/*.rb")
74
+ }
75
+
76
+ #rule '.intout' => ['.intin.rb', *FileList.new("lib/**/*.rb")] do |task| # doesn't work -- see below
77
+ rule '.intout' => ['.intin.rb'] do |task|
78
+ this_file_re = Regexp.compile(Regexp.quote(__FILE__))
79
+ b = binding
80
+ visible=true; visible_retval=true; handle_exceptions=false
81
+ old_stdout = $stdout
82
+ old_wd = Dir.pwd
83
+ begin
84
+ File.open(task.name,"w") do |fout|
85
+ $stdout = fout
86
+ File.open(task.source,"r") do |fin|
87
+ Dir.chdir File.dirname(task.name)
88
+ fin.read.split("#<=\n").each do |snippet|
89
+
90
+ snippet.scan(/^#:(.*?):$/) do |(switch,)|
91
+ case switch
92
+ when "visible"
93
+ visible=true
94
+ when "invisible"
95
+ visible=false
96
+ when "visible_retval"
97
+ visible_retval=true
98
+ when "invisible_retval"
99
+ visible_retval=false
100
+ when "handle_exceptions"
101
+ handle_exceptions=true
102
+ when "no_exceptions"
103
+ handle_exceptions=false
104
+ end
105
+ end
106
+ snippet.gsub!(/^#:.*?:(?:\n|\z)/,'')
107
+
108
+ print "#{snippet}\n" if visible
109
+ exc_handled = false
110
+ value = begin
111
+ eval(snippet,b)
112
+ rescue Exception
113
+ raise unless handle_exceptions
114
+ exc_handled = true
115
+ if visible
116
+ print "#{$!.class}: #{$!}\n"
117
+ for m in $@
118
+ break if m=~this_file_re
119
+ print "\tfrom #{m}\n"
120
+ end
121
+ end
122
+ end
123
+ if visible and visible_retval and not exc_handled
124
+ print "=> #{value.inspect}\n"
125
+ end
126
+ end
127
+ end
128
+ end
129
+ rescue Exception
130
+ $stdout = old_stdout
131
+ Dir.chdir old_wd
132
+ File.delete task.name
133
+ raise
134
+ ensure
135
+ $stdout = old_stdout
136
+ Dir.chdir old_wd
137
+ end
138
+ end
139
+
140
+ # have to add additional prerequisites manually because it appears
141
+ # that rules can only define a single prerequisite :-\
142
+ for f in %w{examples/company_usage
143
+ examples/order_usage
144
+ examples/order_signature_enhanced_usage
145
+ examples/time_augm
146
+ examples/xpath_usage
147
+ examples/xpath_ensure_created
148
+ examples/xpath_create_new
149
+ examples/xpath_pathological
150
+ examples/xpath_docvsroot} do
151
+ file "#{f}.intout" => ["#{f}.intin.rb", 'examples/company.xml']
152
+ file "#{f}.intout" => FileList.new("lib/**/*.rb")
153
+ file "#{f}.intout" => FileList.new("examples/**/*.rb")
154
+ end
155
+
156
+
157
+ spec = Gem::Specification.new do |s|
158
+ s.name = 'xml-mapping'
159
+ s.version = XML::Mapping::VERSION
160
+ s.platform = Gem::Platform::RUBY
161
+ s.summary =
162
+ "An easy to use, extensible library for mapping Ruby objects to XML and back. Includes an XPath interpreter."
163
+
164
+ # Rubygems' RDoc support is incomplete... Can't seem to find a way
165
+ # to set the start page, or a set of files that should be includable
166
+ # but not processed by rdoc directly
167
+ s.files += FILES_RDOC_EXTRA
168
+ s.files += Dir.glob("{lib,examples,test}/**/*").delete_if do |item|
169
+ item.include?("CVS") || item =~ /~$/
170
+ end
171
+ s.files += %w{LICENSE Rakefile install.rb}
172
+ s.extra_rdoc_files = FILES_RDOC_EXTRA
173
+ s.rdoc_options += %w{--include examples}
174
+
175
+ s.require_path = 'lib'
176
+ s.autorequire = 'xml/mapping'
177
+
178
+ # s.add_dependency 'rexml'
179
+
180
+ s.has_rdoc=true
181
+
182
+ s.test_file = 'test/all_tests.rb'
183
+
184
+ s.author = 'Olaf Klischat'
185
+ s.email = 'klischat@cs.tu-berlin.de'
186
+ s.homepage = "http://xml-mapping.rubyforge.org"
187
+ end
188
+
189
+
190
+
191
+ Rake::GemPackageTask.new(spec) do |p|
192
+ p.gem_spec = spec
193
+ p.need_tar = true
194
+ p.need_zip = true
195
+
196
+ # (indirectly) add :rdoc, :test as prerequisites to :package task
197
+ # created by GemPackageTask
198
+ file "#{p.package_dir}/#{p.tgz_file}" => [ "test_run", :rdoc ]
199
+ file "#{p.package_dir}/#{p.zip_file}" => [ "test_run", :rdoc ]
200
+ file "#{p.package_dir}/#{p.gem_file}" => [ "test_run", :rdoc ]
201
+ end
202
+
203
+
204
+
205
+ # run "rake package" to generate tgz, zip, gem in pkg/
206
+
207
+
208
+
209
+ task :rfpub_rdoc => [:rdoc] do
210
+ p=Rake::SshDirPublisher.new('xml-mapping.rubyforge.org',
211
+ '/var/www/gforge-projects/xml-mapping/',
212
+ 'doc/api')
213
+ p.upload
214
+ end
@@ -0,0 +1,32 @@
1
+ - XML::XXPath: generalize foo[@x='bar'] to foo[<any XPath
2
+ expression>='bar'] (unless create/create_new implementation proves
3
+ to be too difficult, but I don't think it will...)
4
+
5
+ - documentation:
6
+
7
+ - README:
8
+
9
+ - multi-attribute nodes
10
+
11
+ - XML::Mapping: Move @options hash functionality from
12
+ SingleAttributeNode to Node.
13
+
14
+ - XML::Mapping/default attribute values: Update documentation, digest
15
+ "nil" issues...
16
+
17
+ - add streaming input/output to XML::Mapping, i.e. SAX-based input in
18
+ addition to the current REXML/DOM - based one. Probably won't be
19
+ implementable for some more complicated XPaths -- raise meaningful
20
+ exceptions in those cases.
21
+
22
+ - XML::XXPath/XML::Mapping: add XML text nodes (the sub-node of an
23
+ element node that contains that element's text) first-class to
24
+ XML::XXPath. Use it for things like text_node :contents, "text()".
25
+
26
+ Along those lines: promote XPath node "unspecifiedness" from an
27
+ attribute to a REXML node object of "unspecified" class that's
28
+ turned into an attribute/element/text node when necessary
29
+
30
+ - (eventually, maybe) provide a "scaffolding" feature to automatically
31
+ turn a dtd/schema into a set of node type definitions or even a set
32
+ of mapping classes
@@ -0,0 +1,119 @@
1
+ === latest design (12/2004)
2
+
3
+ At the lowest level, the "Accessors" sub-module contains reader and
4
+ creator functions that correspond to the various types of path
5
+ elements (_elt_name_, @_attr_name_,
6
+ _elt_name_[@_attr_name_='_attr_value_'] etc.) that xml-xxpath
7
+ supports. A reader function gets an array of nodes and the search
8
+ parameters corresponding to its path element type (e.g. _elt_name_,
9
+ _attr_name_, _attr_value_) and returns an array with all matching
10
+ direct sub-nodes of any of the supplied nodes. A creator function gets
11
+ one node and the search parameters and returns the created sub-node.
12
+
13
+ An XPath expression <tt><things1>/<things2>/.../<thingsx></tt> is
14
+ compiled into a bunch of nested closures, each of which is responsible
15
+ for a specific path element and calls the corresponding accessor
16
+ function:
17
+
18
+ - <tt>@creator_procs</tt> -- an array of "creator"
19
+ functions. <tt>@creator_procs[i]</tt> gets passed a base node (XML
20
+ element) and a create_new flag, and it creates the path
21
+ <tt><things[x-i+1]>/<things[x-i+2]>/.../<thingsx></tt> inside the
22
+ base node and returns the hindmost element created (i.e. the one
23
+ corresponding to <tt><thingsx></tt>).
24
+
25
+ - <tt>@reader_proc</tt> -- a "reader" function that gets passed an
26
+ array of nodes and returns an array of all nodes that matched the
27
+ path in any of the supplied nodes, or, if no match was found, throws
28
+ :not_found along with the last non-empty set of nodes that was
29
+ found, and the element of <tt>@creator_procs</tt> that could be used
30
+ to create the remaining part of the path.
31
+
32
+ The +all+ function is then trivially implemented on top of this:
33
+
34
+ def all(node,options={})
35
+ raise "options not a hash" unless Hash===options
36
+ if options[:create_new]
37
+ return [ @creator_procs[-1].call(node,true) ]
38
+ else
39
+ last_nodes,rest_creator = catch(:not_found) do
40
+ return @reader_proc.call([node])
41
+ end
42
+ if options[:ensure_created]
43
+ [ rest_creator.call(last_nodes[0],false) ]
44
+ else
45
+ []
46
+ end
47
+ end
48
+ end
49
+
50
+ ...and +first+, <tt>create_new</tt> etc. are even more trivial
51
+ frontends to that.
52
+
53
+ The implementations of the <tt>@creator_procs</tt> look like this:
54
+
55
+ @creator_procs[0] =
56
+ proc{|node,create_new| node}
57
+
58
+ @creator_procs[1] =
59
+ proc {|node,create_new|
60
+ @creator_procs[0].call(Accessors.create_subnode_by_<thingsx>(node,create_new,<thingsx>),
61
+ create_new)
62
+ }
63
+
64
+ @creator_procs[2] =
65
+ proc {|node,create_new|
66
+ @creator_procs[1].call(Accessors.create_subnode_by_<thingsx-1>(node,create_new,<thingsx-1>),
67
+ create_new)
68
+ }
69
+
70
+ ...
71
+
72
+ @creator_procs[n] =
73
+ proc {|node,create_new|
74
+ @creator_procs[n-1].call(Accessors.create_subnode_by_<things[x+1-n]>(node,create_new,<things[x+1-n]>),
75
+ create_new)
76
+ }
77
+
78
+ ...
79
+ @creator_procs[x] =
80
+ proc {|node,create_new|
81
+ @creator_procs[x-1].call(Accessors.create_subnode_by_<things1>(node,create_new,<things1>),
82
+ create_new)
83
+ }
84
+
85
+
86
+
87
+ ..and the implementation of @reader_proc looks like this:
88
+
89
+ @reader_proc = rpx where
90
+
91
+ rp0 = proc {|nodes| nodes}
92
+
93
+ rp1 = proc {|nodes|
94
+ next_nodes = Accessors.subnodes_by_<thingsx>(nodes,<thingsx>)
95
+ if (next_nodes == [])
96
+ throw :not_found, [nodes,@creator_procs[1]]
97
+ else
98
+ rp0.call(next_nodes)
99
+ end
100
+ }
101
+
102
+ rp2 = proc {|nodes|
103
+ next_nodes = Accessors.subnodes_by_<thingsx-1>(nodes,<thingsx-1>)
104
+ if (next_nodes == [])
105
+ throw :not_found, [nodes,@creator_procs[2]]
106
+ else
107
+ rp1.call(next_nodes)
108
+ end
109
+ }
110
+ ...
111
+
112
+ rpx = proc {|nodes|
113
+ next_nodes = Accessors.subnodes_by_<things1>(nodes,<things1>)
114
+ if (next_nodes == [])
115
+ throw :not_found, [nodes,@creator_procs[x]]
116
+ else
117
+ rpx-1.call(next_nodes)
118
+ end
119
+ }
@@ -0,0 +1,34 @@
1
+ require 'xml/mapping'
2
+
3
+ ## forward declarations
4
+ class Address; end
5
+ class Customer; end
6
+
7
+
8
+ class Company
9
+ include XML::Mapping
10
+
11
+ text_node :name, "@name"
12
+ object_node :address, "address", :class=>Address
13
+ array_node :customers, "customers", "customer", :class=>Customer
14
+ end
15
+
16
+
17
+ class Address
18
+ include XML::Mapping
19
+
20
+ text_node :city, "city"
21
+ numeric_node :zip, "zip"
22
+ end
23
+
24
+
25
+ class Customer
26
+ include XML::Mapping
27
+
28
+ text_node :id, "@id"
29
+ text_node :name, "name"
30
+
31
+ def initialize(id,name)
32
+ @id,@name = [id,name]
33
+ end
34
+ end
@@ -0,0 +1,26 @@
1
+ <?xml version="1.0" encoding="ISO-8859-1"?>
2
+
3
+ <company name="ACME inc.">
4
+
5
+ <address>
6
+ <city>Berlin</city>
7
+ <zip>10113</zip>
8
+ </address>
9
+
10
+ <customers>
11
+
12
+ <customer id="jim">
13
+ <name>James Kirk</name>
14
+ </customer>
15
+
16
+ <customer id="ernie">
17
+ <name>Ernie</name>
18
+ </customer>
19
+
20
+ <customer id="bert">
21
+ <name>Bert</name>
22
+ </customer>
23
+
24
+ </customers>
25
+
26
+ </company>
@@ -0,0 +1,19 @@
1
+ #:invisible:
2
+ $:.unshift "../lib"
3
+ begin
4
+ Object.send(:remove_const, "Address") # name clash with order_usage...
5
+ rescue
6
+ end
7
+ require 'company' #<=
8
+ #:visible:
9
+ c = Company.load_from_file('company.xml') #<=
10
+ c.name #<=
11
+ c.customers.size #<=
12
+ c.customers[1] #<=
13
+ c.customers[1].name #<=
14
+ c.customers[0].name #<=
15
+ c.customers[0].name = 'James Tiberius Kirk' #<=
16
+ c.customers << Customer.new('cm','Cookie Monster') #<=
17
+ xml2 = c.save_to_xml #<=
18
+ #:invisible_retval:
19
+ xml2.write($stdout,2) #<=