xml-mapping 0.8

Sign up to get free protection for your applications and to get access to all the features.
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) #<=