ROXML 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. data/.gitignore +6 -0
  2. data/.gitmodules +3 -0
  3. data/History.txt +299 -0
  4. data/MIT-LICENSE +18 -0
  5. data/README.rdoc +161 -0
  6. data/Rakefile +95 -0
  7. data/TODO +39 -0
  8. data/VERSION +1 -0
  9. data/config/website.yml +2 -0
  10. data/examples/amazon.rb +35 -0
  11. data/examples/current_weather.rb +27 -0
  12. data/examples/dashed_elements.rb +20 -0
  13. data/examples/library.rb +40 -0
  14. data/examples/posts.rb +27 -0
  15. data/examples/rails.rb +70 -0
  16. data/examples/twitter.rb +37 -0
  17. data/examples/xml/active_record.xml +70 -0
  18. data/examples/xml/amazon.xml +133 -0
  19. data/examples/xml/current_weather.xml +89 -0
  20. data/examples/xml/dashed_elements.xml +52 -0
  21. data/examples/xml/posts.xml +23 -0
  22. data/examples/xml/twitter.xml +422 -0
  23. data/lib/roxml.rb +547 -0
  24. data/lib/roxml/definition.rb +236 -0
  25. data/lib/roxml/hash_definition.rb +25 -0
  26. data/lib/roxml/xml.rb +43 -0
  27. data/lib/roxml/xml/parsers/libxml.rb +91 -0
  28. data/lib/roxml/xml/parsers/nokogiri.rb +77 -0
  29. data/lib/roxml/xml/references.rb +297 -0
  30. data/roxml.gemspec +201 -0
  31. data/spec/definition_spec.rb +486 -0
  32. data/spec/examples/active_record_spec.rb +40 -0
  33. data/spec/examples/amazon_spec.rb +54 -0
  34. data/spec/examples/current_weather_spec.rb +37 -0
  35. data/spec/examples/dashed_elements_spec.rb +20 -0
  36. data/spec/examples/library_spec.rb +46 -0
  37. data/spec/examples/post_spec.rb +24 -0
  38. data/spec/examples/twitter_spec.rb +32 -0
  39. data/spec/roxml_spec.rb +372 -0
  40. data/spec/shared_specs.rb +15 -0
  41. data/spec/spec.opts +1 -0
  42. data/spec/spec_helper.rb +14 -0
  43. data/spec/support/libxml.rb +3 -0
  44. data/spec/support/nokogiri.rb +3 -0
  45. data/spec/xml/attributes_spec.rb +36 -0
  46. data/spec/xml/namespace_spec.rb +240 -0
  47. data/spec/xml/namespaces_spec.rb +32 -0
  48. data/spec/xml/parser_spec.rb +26 -0
  49. data/tasks/rdoc.rake +13 -0
  50. data/tasks/rspec.rake +25 -0
  51. data/tasks/test.rake +35 -0
  52. data/test/fixtures/book_malformed.xml +5 -0
  53. data/test/fixtures/book_pair.xml +8 -0
  54. data/test/fixtures/book_text_with_attribute.xml +5 -0
  55. data/test/fixtures/book_valid.xml +5 -0
  56. data/test/fixtures/book_with_authors.xml +7 -0
  57. data/test/fixtures/book_with_contributions.xml +9 -0
  58. data/test/fixtures/book_with_contributors.xml +7 -0
  59. data/test/fixtures/book_with_contributors_attrs.xml +7 -0
  60. data/test/fixtures/book_with_default_namespace.xml +9 -0
  61. data/test/fixtures/book_with_depth.xml +6 -0
  62. data/test/fixtures/book_with_octal_pages.xml +4 -0
  63. data/test/fixtures/book_with_publisher.xml +7 -0
  64. data/test/fixtures/book_with_wrapped_attr.xml +3 -0
  65. data/test/fixtures/dictionary_of_attr_name_clashes.xml +8 -0
  66. data/test/fixtures/dictionary_of_attrs.xml +6 -0
  67. data/test/fixtures/dictionary_of_guarded_names.xml +6 -0
  68. data/test/fixtures/dictionary_of_mixeds.xml +4 -0
  69. data/test/fixtures/dictionary_of_name_clashes.xml +10 -0
  70. data/test/fixtures/dictionary_of_names.xml +4 -0
  71. data/test/fixtures/dictionary_of_texts.xml +10 -0
  72. data/test/fixtures/library.xml +30 -0
  73. data/test/fixtures/library_uppercase.xml +30 -0
  74. data/test/fixtures/muffins.xml +3 -0
  75. data/test/fixtures/nameless_ageless_youth.xml +2 -0
  76. data/test/fixtures/node_with_attr_name_conflicts.xml +1 -0
  77. data/test/fixtures/node_with_name_conflicts.xml +4 -0
  78. data/test/fixtures/numerology.xml +4 -0
  79. data/test/fixtures/person.xml +1 -0
  80. data/test/fixtures/person_with_guarded_mothers.xml +13 -0
  81. data/test/fixtures/person_with_mothers.xml +10 -0
  82. data/test/mocks/dictionaries.rb +57 -0
  83. data/test/mocks/mocks.rb +279 -0
  84. data/test/support/fixtures.rb +11 -0
  85. data/test/test_helper.rb +34 -0
  86. data/test/unit/definition_test.rb +235 -0
  87. data/test/unit/deprecations_test.rb +24 -0
  88. data/test/unit/to_xml_test.rb +81 -0
  89. data/test/unit/xml_attribute_test.rb +39 -0
  90. data/test/unit/xml_block_test.rb +81 -0
  91. data/test/unit/xml_bool_test.rb +122 -0
  92. data/test/unit/xml_convention_test.rb +150 -0
  93. data/test/unit/xml_hash_test.rb +115 -0
  94. data/test/unit/xml_initialize_test.rb +49 -0
  95. data/test/unit/xml_name_test.rb +141 -0
  96. data/test/unit/xml_namespace_test.rb +31 -0
  97. data/test/unit/xml_object_test.rb +207 -0
  98. data/test/unit/xml_required_test.rb +94 -0
  99. data/test/unit/xml_text_test.rb +71 -0
  100. data/website/index.html +98 -0
  101. metadata +254 -0
@@ -0,0 +1,95 @@
1
+ require 'rake'
2
+
3
+ ENV['RUBY_FLAGS'] = '-W1'
4
+
5
+ # Generate all the Rake tasks
6
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
7
+ require 'jeweler'
8
+ Jeweler::Tasks.new do |gem|
9
+ gem.name = 'ROXML'
10
+ gem.rubyforge_project = "roxml"
11
+ gem.summary = "Ruby Object to XML mapping library"
12
+ gem.description = <<EOF
13
+ ROXML is a Ruby library designed to make it easier for Ruby developers to work with XML.
14
+ Using simple annotations, it enables Ruby classes to be mapped to XML. ROXML takes care
15
+ of the marshalling and unmarshalling of mapped attributes so that developers can focus on
16
+ building first-class Ruby classes. As a result, ROXML simplifies the development of
17
+ RESTful applications, Web Services, and XML-RPC.
18
+ EOF
19
+ gem.email = "ben.woosley@gmail.com"
20
+ gem.homepage = "http://roxml.rubyforge.org"
21
+ gem.authors = ["Ben Woosley", "Zak Mandhro", "Anders Engstrom", "Russ Olsen"]
22
+
23
+ gem.extra_rdoc_files = ["History.txt", "Manifest.txt", "README.rdoc"]
24
+
25
+ gem.add_dependency 'activesupport', '>= 2.1.0'
26
+ gem.add_dependency 'nokogiri', '>= 1.3.3'
27
+
28
+ gem.add_development_dependency "rspec"
29
+ gem.add_development_dependency "sqlite3-ruby", '>= 1.2.4'
30
+ gem.add_development_dependency "activerecord", '>= 2.2.2'
31
+ end
32
+ Jeweler::GemcutterTasks.new
33
+ Jeweler::RubyforgeTasks.new do |rubyforge|
34
+ rubyforge.doc_task = "rdoc"
35
+ end
36
+
37
+ Dir['tasks/**/*.rake'].each { |t| load t }
38
+
39
+ task :default => [:test, :spec]
40
+ task :all => [:libxml, :nokogiri]
41
+ task :libxml => ['test:libxml', 'spec:libxml']
42
+ task :nokogiri => ['test:nokogiri', 'spec:nokogiri']
43
+
44
+ # $hoe = Hoe.new('roxml', ROXML::VERSION) do |p|
45
+ # p.changes = File.read("History.txt").split(/^==/)[1].strip
46
+ #
47
+ # p.test_globs = 'test/unit/*_test.rb'
48
+ # p.clean_globs |= %w[**/.DS_Store tmp *.log]
49
+ # path = (p.rubyforge_name == p.name) ? p.rubyforge_name : "\#{p.rubyforge_name}/\#{p.name}"
50
+ # p.remote_rdoc_dir = File.join(path.gsub(/^#{p.rubyforge_name}\/?/,''), 'rdoc')
51
+ # p.rsync_args = '-av --delete --ignore-errors'
52
+ # end
53
+
54
+ ## Provide the username used to upload website etc.
55
+ #RubyForgeConfig = {
56
+ # :unix_name=>"roxml",
57
+ # :user_name=>"zakmandhro"
58
+ #}
59
+
60
+ #Rake::RDocTask.new do |rd|
61
+ # rd.rdoc_dir = "doc"
62
+ # rd.rdoc_files.include('MIT-LICENSE', 'README.rdoc', "lib/**/*.rb")
63
+ # rd.options << '--main' << 'README.rdoc' << '--title' << 'ROXML Documentation'
64
+ #end
65
+ #
66
+ #desc "Publish Ruby on Rails plug-in on RubyForge"
67
+ #task :release_plugin=>:rails_plugin do |task|
68
+ # pub = Rake::SshDirPublisher.new("#{RubyForgeConfig[:user_name]}@rubyforge.org",
69
+ # "/var/www/gforge-projects/#{RubyForgeConfig[:unix_name]}",
70
+ # "pkg/rails_plugin")
71
+ # pub.upload()
72
+ #end
73
+ #
74
+ #desc "Publish and plugin site on RubyForge"
75
+ #task :publish do |task|
76
+ # pub = Rake::RubyForgePublisher.new(RubyForgeConfig[:unix_name], RubyForgeConfig[:user_name])
77
+ # pub.upload()
78
+ #end
79
+ #
80
+ #desc "Create the ZIP package"
81
+ #Rake::PackageTask.new(spec.name, spec.version) do |p|
82
+ # p.need_zip = true
83
+ # p.package_files = FileList[
84
+ # "lib/**/*.rb", "*.txt", "README.rdoc", "Rakefile",
85
+ # "rake/**/*","test/**/*.rb", "test/**/*.xml", "html/**/*"]
86
+ #end
87
+ #
88
+ #task :package=>:rdoc
89
+ #task :rdoc=>:test
90
+ #
91
+ #desc "Create a RubyGem project"
92
+ #Rake::GemPackageTask.new(spec).define
93
+ #
94
+ #desc "Clobber generated files"
95
+ #task :clobber=>[:clobber_package, :clobber_rdoc]
data/TODO ADDED
@@ -0,0 +1,39 @@
1
+ Planned:
2
+
3
+ v 3.1
4
+
5
+ * Add xml_namespaces to allow declaration of distinct prefx/url namespace pairs
6
+
7
+ * Consider class_inheritable_attribute rather than superclass.try stuff.
8
+
9
+ * Do some benchmarking
10
+
11
+ * Back with http://xml-object.rubyforge.org/doc/ to minimize need for specifications?
12
+
13
+ * Commandeer #parse to use opposite #from_xml, but in an unrooted, collection-friendly fashion,
14
+ ala HappyMapper's parse
15
+
16
+ v 3.x
17
+
18
+ * :self => true for sending method_missing to this attribute?
19
+
20
+ * :attributes extensions ala HappyMapper?
21
+
22
+ * Add xml_attrs helper to share :in declarations between several attributes. E.g.:
23
+
24
+ xml_reader :count, :in => 'Attributes', :as => Integer
25
+ xml_reader :something_else, :in => 'Attributes', :as => Date
26
+
27
+ becomes:
28
+
29
+ xml_attrs :in => 'Attributes' do |xml|
30
+ xml.reader :count, :as => Integer
31
+ xml.reader :something_else, :as => Date
32
+ end
33
+
34
+ * Ensure (perhaps optionally) that references are unambiguous. That is error/warn
35
+ a singular specification has multiple possible node references
36
+
37
+ * Use lazy evaluation to minimize parsing time for large files
38
+
39
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 3.0.0
@@ -0,0 +1,2 @@
1
+ host: empact@rubyforge.org
2
+ remote_dir: /var/www/gforge-projects/roxml
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env ruby
2
+ require 'spec/spec_helper'
3
+
4
+ # The document `pita.xml` contains both a default namespace and the 'georss'
5
+ # namespace (for the 'point' xml_reader).
6
+ module PITA
7
+ class Base
8
+ include ROXML
9
+ xml_convention :camelcase
10
+ end
11
+
12
+ class Item < Base
13
+ xml_reader :asin, :from => 'ASIN'
14
+ xml_reader :detail_page_url, :from => 'DetailPageURL'
15
+ xml_reader :manufacturer, :in => 'ItemAttributes'
16
+ # this is the only xml_reader that exists in a different namespace, so it
17
+ # must be explicitly specified
18
+ xml_reader :point, :namespace => 'georss'
19
+ end
20
+
21
+ class ItemSearchResponse < Base
22
+ xml_reader :total_results, :as => Integer, :in => 'Items'
23
+ xml_reader :total_pages, :as => Integer, :in => 'Items'
24
+ xml_reader :items, :as => [Item]
25
+ end
26
+ end
27
+
28
+ unless defined?(Spec)
29
+ response = PITA::ItemSearchResponse.from_xml(xml_for('amazon'))
30
+ p response.total_results
31
+ p response.total_pages
32
+ response.items.each do |i|
33
+ puts i.asin, i.detail_page_url, i.manufacturer, i.point, ''
34
+ end
35
+ end
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ require File.expand_path(File.dirname(__FILE__) + '/../spec/spec_helper')
3
+
4
+ class Base
5
+ include ROXML
6
+ xml_convention :dasherize
7
+ xml_namespace 'aws'
8
+ end
9
+
10
+ class WeatherObservation < Base
11
+ xml_name 'ob'
12
+ xml_reader :temperature, :as => Float, :from => 'aws:temp'
13
+ xml_reader :feels_like, :as => Integer
14
+ xml_reader :current_condition #, :attributes => {:icon => String} # pending
15
+ end
16
+
17
+ class Weather < Base
18
+ xml_reader :observation, :as => WeatherObservation, :required => true
19
+ end
20
+
21
+ unless defined?(Spec)
22
+ current_weather = Weather.from_xml(xml_for('current_weather')).observation
23
+ puts "temperature: #{current_weather.temperature}"
24
+ puts "feels_like: #{current_weather.feels_like}"
25
+ puts "current_condition: #{current_weather.current_condition}"
26
+ # puts "current_condition.icon: #{current_weather.current_condition.icon}" # pending
27
+ end
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+ require File.expand_path(File.dirname(__FILE__) + '/../spec/spec_helper')
3
+
4
+ module GitHub
5
+ class Commit
6
+ include ROXML
7
+ xml_convention :dasherize
8
+
9
+ xml_reader :url
10
+ xml_reader :tree
11
+ xml_reader :message
12
+ xml_reader :id
13
+ xml_reader :committed_date, :as => Date
14
+ end
15
+ end
16
+
17
+ unless defined?(Spec)
18
+ commit = GitHub::Commit.from_xml(xml_for('dashed_elements'))
19
+ puts commit.committed_date, commit.url, commit.id, ''
20
+ end
@@ -0,0 +1,40 @@
1
+ class Publisher
2
+ include ROXML
3
+
4
+ xml_accessor :name
5
+
6
+ def initialize(name = nil)
7
+ @name = name
8
+ end
9
+
10
+ def ==(other)
11
+ name == other.name
12
+ end
13
+
14
+ # other important functionality
15
+ end
16
+
17
+ class Novel
18
+ include ROXML
19
+
20
+ xml_accessor :isbn, :from => "@ISBN" # attribute with name 'ISBN'
21
+ xml_accessor :title
22
+ xml_accessor :description, :cdata => true # text node with cdata protection
23
+ xml_accessor :author
24
+ xml_accessor :publisher, :as => Publisher # singular object reference for illustrative purposes.
25
+
26
+ def ==(other)
27
+ self.class.roxml_attrs.map(&:accessor).all? {|attr| send(attr) == other.send(attr) }
28
+ end
29
+ end
30
+
31
+ class Library
32
+ include ROXML
33
+
34
+ xml_accessor :name, :from => "NAME", :cdata => true
35
+ xml_accessor :novels, :as => [Novel] # by default roxml searches for books for in <novel> children, then, if none are present, in ./novels/novel children
36
+
37
+ def ==(other)
38
+ name == other.name && novels == other.novels
39
+ end
40
+ end
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ require File.expand_path(File.dirname(__FILE__) + '/../spec/spec_helper')
3
+
4
+ class Post
5
+ include ROXML
6
+
7
+ xml_reader :href, :from => :attr
8
+ xml_reader :hash, :from => :attr
9
+ xml_reader :description, :from => :attr
10
+ xml_reader :tag, :from => :attr
11
+ xml_reader :created_at, :from => '@time'
12
+ xml_reader :others, :from => :attr, :as => Integer
13
+ xml_reader :extended, :from => :attr
14
+ end
15
+
16
+ class Posts
17
+ include ROXML
18
+
19
+ xml_reader :posts, :as => [Post]
20
+ end
21
+
22
+ unless defined?(Spec)
23
+ posts = Posts.from_xml(xml_for('posts'))
24
+ posts.posts.each do |post|
25
+ puts post.description, post.href, post.extended, ''
26
+ end
27
+ end
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env ruby
2
+ require 'spec/spec_helper'
3
+ require 'sqlite3'
4
+ require 'activerecord'
5
+
6
+ DB_PATH = 'spec/examples/rails.sqlite3'
7
+ ActiveRecord::Base.establish_connection(
8
+ :adapter => 'sqlite3',
9
+ :database => DB_PATH
10
+ )
11
+
12
+ class Waypoint < ActiveRecord::Base
13
+ include ROXML
14
+
15
+ belongs_to :route
16
+
17
+ xml_attr :isLeg
18
+ xml_attr :lonlatx
19
+ xml_attr :lonlaty
20
+ xml_attr :gridReference
21
+ xml_attr :ascent
22
+ xml_attr :descent
23
+ xml_attr :distance
24
+ xml_attr :bearing
25
+ xml_attr :timemins
26
+ end
27
+
28
+ class Route < ActiveRecord::Base
29
+ include ROXML
30
+
31
+ has_many :waypoints
32
+
33
+ xml_attr :title
34
+ xml_attr :totalDist
35
+ xml_attr :totalMins
36
+ xml_attr :totalHg
37
+ xml_attr :lonlatx
38
+ xml_attr :lonlaty
39
+ xml_attr :grcenter
40
+
41
+ xml_attr :waypoints, :as => [Waypoint], :in => "waypoints"
42
+ end
43
+
44
+ # do a quick pseudo migration. This should only get executed on the first run
45
+ if !Waypoint.table_exists?
46
+ ActiveRecord::Base.connection.create_table(:waypoints) do |t|
47
+ t.column :route_id, :integer
48
+ t.column :isLeg, :string
49
+ t.column :lonlatx, :string
50
+ t.column :lonlaty, :string
51
+ t.column :gridReference, :string
52
+ t.column :ascent, :string
53
+ t.column :descent, :string
54
+ t.column :distance, :string
55
+ t.column :bearing, :string
56
+ t.column :timeMins, :string
57
+ end
58
+ end
59
+
60
+ if !Route.table_exists?
61
+ ActiveRecord::Base.connection.create_table(:routes) do |t|
62
+ t.column :title, :string
63
+ t.column :totalDist, :string
64
+ t.column :totalMins, :string
65
+ t.column :totalHg, :string
66
+ t.column :lonlatx, :string
67
+ t.column :lonlaty, :string
68
+ t.column :grcenter, :string
69
+ end
70
+ end
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+ require File.expand_path(File.dirname(__FILE__) + '/../spec/spec_helper')
3
+ require 'time'
4
+
5
+ class User
6
+ include ROXML
7
+
8
+ xml_reader :id, :as => Integer
9
+ xml_reader :name
10
+ xml_reader :screen_name
11
+ xml_reader :location
12
+ xml_reader :description
13
+ xml_reader :profile_image_url
14
+ xml_reader :url
15
+ xml_reader :protected?
16
+ xml_reader :followers_count, :as => Integer
17
+ end
18
+
19
+ class Status
20
+ include ROXML
21
+
22
+ xml_reader :id, :as => Integer
23
+ xml_reader :text
24
+ xml_reader :created_at # This defaults to :as => DateTime, due to the '_at'
25
+ xml_reader :source
26
+ xml_reader :truncated?
27
+ xml_reader :in_reply_to_status_id, :as => Integer
28
+ xml_reader :in_reply_to_user_id, :as => Integer
29
+ xml_reader :favorited?
30
+ xml_reader :user, :as => User
31
+ end
32
+
33
+ class Statuses
34
+ include ROXML
35
+
36
+ xml_reader :statuses, :as => [Status]
37
+ end
@@ -0,0 +1,70 @@
1
+ <route>
2
+ <totalDist>11185.321521477119</totalDist>
3
+ <totalHg>640</totalHg>
4
+ <totalMins>235.75000000000003</totalMins>
5
+ <lonlatx>357865</lonlatx>
6
+ <lonlaty>271635</lonlaty>
7
+ <grcenter>SH 71635 57865</grcenter>
8
+ <waypoints>
9
+ <waypoint>
10
+ <isLeg>false</isLeg>
11
+ <lonlatx>357290</lonlatx>
12
+ <lonlaty>271650</lonlaty>
13
+ <gridReference>SH 71650 57290</gridReference>
14
+ <ascent>81</ascent>
15
+ <descent>220</descent>
16
+ <distance>0</distance>
17
+ <bearing>0</bearing>
18
+ </waypoint>
19
+ <waypoint>
20
+ <isLeg>false</isLeg>
21
+ <lonlatx>357260</lonlatx>
22
+ <lonlaty>274600</lonlaty>
23
+ <gridReference>SH 74600 57260</gridReference>
24
+ <ascent>275</ascent>
25
+ <descent>48</descent>
26
+ <distance>2950.152538429157</distance>
27
+ <bearing>91</bearing>
28
+ </waypoint>
29
+ <waypoint>
30
+ <isLeg>false</isLeg>
31
+ <lonlatx>359160</lonlatx>
32
+ <lonlaty>273330</lonlaty>
33
+ <gridReference>SH 73330 59160</gridReference>
34
+ <ascent>73</ascent>
35
+ <descent>170</descent>
36
+ <distance>2285.3664913969487</distance>
37
+ <bearing>326</bearing>
38
+ </waypoint>
39
+ <waypoint>
40
+ <isLeg>false</isLeg>
41
+ <lonlatx>359170</lonlatx>
42
+ <lonlaty>270050</lonlaty>
43
+ <gridReference>SH 70050 59170</gridReference>
44
+ <ascent>182</ascent>
45
+ <descent>172</descent>
46
+ <distance>3280.015243867016</distance>
47
+ <bearing>270</bearing>
48
+ </waypoint>
49
+ <waypoint>
50
+ <isLeg>false</isLeg>
51
+ <lonlatx>357470</lonlatx>
52
+ <lonlaty>269740</lonlaty>
53
+ <gridReference>SH 69740 57470</gridReference>
54
+ <ascent>29</ascent>
55
+ <descent>107</descent>
56
+ <distance>1728.0335644888382</distance>
57
+ <bearing>190</bearing>
58
+ </waypoint>
59
+ <waypoint>
60
+ <isLeg>false</isLeg>
61
+ <lonlatx>356840</lonlatx>
62
+ <lonlaty>270440</lonlaty>
63
+ <gridReference>SH 70440 56840</gridReference>
64
+ <ascent>640</ascent>
65
+ <descent>717</descent>
66
+ <distance>941.7536832951597</distance>
67
+ <bearing>132</bearing>
68
+ </waypoint>
69
+ </waypoints>
70
+ </route>