xommelier 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/.gitignore +1 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +6 -0
  4. data/README.md +19 -0
  5. data/Rakefile +20 -0
  6. data/Termfile +38 -0
  7. data/console +7 -0
  8. data/examples/atom.rb +4 -4
  9. data/lib/xommelier/atom/category.rb +1 -1
  10. data/lib/xommelier/atom/entry.rb +1 -1
  11. data/lib/xommelier/atom/generator.rb +1 -1
  12. data/lib/xommelier/atom/link.rb +1 -1
  13. data/lib/xommelier/atom/person.rb +1 -1
  14. data/lib/xommelier/atom/thread.rb +38 -0
  15. data/lib/xommelier/atom.rb +4 -20
  16. data/lib/xommelier/core_ext/{integer.rb → numeric.rb} +1 -1
  17. data/lib/xommelier/core_ext/symbol.rb +9 -0
  18. data/lib/xommelier/core_ext/uri.rb +11 -5
  19. data/lib/xommelier/core_ext.rb +2 -1
  20. data/lib/xommelier/version.rb +1 -1
  21. data/lib/xommelier/xml/class_methods.rb +1 -0
  22. data/lib/xommelier/xml/element/serialization.rb +148 -0
  23. data/lib/xommelier/xml/element/structure.rb +236 -0
  24. data/lib/xommelier/xml/element.rb +23 -93
  25. data/lib/xommelier/xml/namespace.rb +0 -1
  26. data/lib/xommelier/xml.rb +1 -2
  27. data/spec/fixtures/feed.atom +39 -0
  28. data/spec/fixtures/simple_feed.atom +17 -0
  29. data/spec/lib/xommelier/atom/entry_spec.rb +18 -0
  30. data/spec/lib/xommelier/xml/element/serialization_spec.rb +36 -0
  31. data/spec/lib/xommelier/xml/element/structure_spec.rb +27 -0
  32. data/spec/lib/xommelier/xml/element_spec.rb +21 -38
  33. data/spec/lib/xommelier/xml_spec.rb +2 -4
  34. data/spec/lib/xommelier_spec.rb +1 -1
  35. data/spec/namespaced_module.rb +0 -3
  36. data/spec/spec_helper.rb +5 -3
  37. data/xommelier.gemspec +2 -2
  38. metadata +62 -49
  39. data/lib/xommelier/xml/attribute.rb +0 -8
  40. data/lib/xommelier/xml/element/dsl.rb +0 -51
data/.gitignore CHANGED
@@ -2,3 +2,4 @@
2
2
  .bundle
3
3
  Gemfile.lock
4
4
  pkg/*
5
+ /doc
data/.rspec CHANGED
@@ -1 +1,3 @@
1
1
  --colour
2
+ -Ilib
3
+ -Ispec
data/Gemfile CHANGED
@@ -10,6 +10,8 @@ group :development, :test do
10
10
  gem 'guard'
11
11
  gem 'guard-rspec'
12
12
  gem 'guard-bundler'
13
+ gem 'ruby_gntp'
14
+ gem 'rb-fsevent'
13
15
  end
14
16
 
15
17
  group :development do
@@ -21,3 +23,7 @@ group :development do
21
23
  gem 'ripl-rails', require: 'ripl/rails'
22
24
  gem 'ripl-rocket', require: 'ripl/rocket'
23
25
  end
26
+
27
+ group :documentation do
28
+ gem 'yard'
29
+ end
data/README.md ADDED
@@ -0,0 +1,19 @@
1
+ # Xommelier is an XML Sommelier
2
+
3
+ ## Overview
4
+
5
+ Xommelier is an XML Object Mapper. You could describe some namespace (e.g. Atom) in ruby DSL and use it for parsing XML to Ruby objects or for building XML from Ruby objects.
6
+
7
+ Look into {Xommelier::Atom} module for implementation of http://www.w3.org/2005/Atom namespace
8
+
9
+ Xommelier is work in progress.
10
+
11
+ ## Examples
12
+
13
+ You can see example code in examples/ folder.
14
+
15
+ ## Future tasks
16
+
17
+ * Implementation that support more than one namespace in XML class
18
+ * Converting XML Schema, RelaxNG, RelaxNG Compact and DTD into Xommelier Ruby DSL
19
+ * ActiveRecord-like automatic loading of XML Schema, RelaxNG, RelaxNG Compact and DTD without needing to write it down into ruby code
data/Rakefile CHANGED
@@ -1 +1,21 @@
1
1
  require 'bundler/gem_tasks'
2
+
3
+ begin
4
+ require 'yard'
5
+ YARD::Rake::YardocTask.new(:doc)
6
+ rescue LoadError
7
+ task :doc do
8
+ abort "YARD is not available. In order to run yardoc, you must: gem install yard"
9
+ end
10
+ end
11
+
12
+ begin
13
+ require 'rspec/core/rake_task'
14
+ RSpec::Core::RakeTask.new(:spec)
15
+ rescue LoadError
16
+ task :spec do
17
+ abort "RSpec is not available. In order to run specs, you must: gem install rspec"
18
+ end
19
+ end
20
+
21
+ task default: :spec
data/Termfile ADDED
@@ -0,0 +1,38 @@
1
+ # IDE for rails application development
2
+
3
+ #setup 'bundle'
4
+
5
+ def title(title)
6
+ run "echo -e \"\\033];#{title}\\007\""
7
+ end
8
+
9
+ title 'Editor'
10
+ run 'vim Gemfile'
11
+
12
+ tab(:bash) do
13
+ title('Shell')
14
+ end
15
+ #tab(:log) do
16
+ #title 'Logs'
17
+ #run 'tail -f log/development.log'
18
+ #end
19
+ tab :guard do
20
+ title 'Guard'
21
+ run 'guard'
22
+ end
23
+ tab(:console) do
24
+ title 'Console'
25
+ run './console'
26
+ end
27
+
28
+ #window do
29
+ #tab "echo 'first tab'", "echo 'of window'"
30
+ #tab "named tab", :settings => "Ocean" do
31
+ #run "echo 'named tab'"
32
+ #run "ls"
33
+ #end
34
+ #end
35
+
36
+ #window "autotest" do
37
+ #tab "echo 'window and tab without options'"
38
+ #end
data/console ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ require 'bundler/setup'
3
+ require 'xommelier'
4
+ require 'ripl'
5
+ require 'pp'
6
+
7
+ Ripl.start
data/examples/atom.rb CHANGED
@@ -1,11 +1,11 @@
1
1
  require 'xommelier/atom'
2
- require 'open-uri'
2
+ require 'active_support/core_ext'
3
3
 
4
4
  # Reading a feed
5
- feed = Xommelier::Atom::Feed.parse(open('http://example.com/blog.atom'))
5
+ feed = Xommelier::Atom::Feed.parse(open('spec/fixtures/feed.atom'))
6
6
  puts feed.id, feed.title, feed.updated
7
7
 
8
- feed.each(:entry) do |entry|
8
+ feed.entries do |entry|
9
9
  puts feed.id, feed.title, feed.published, feed.updated
10
10
  puts feed.content || feed.summary
11
11
  end
@@ -21,7 +21,7 @@ feed.title = 'Example.com blog'
21
21
  entry.title = "Example.com blog entry #{i}"
22
22
  entry.updated = (5 - i).days.ago
23
23
 
24
- feed << entry
24
+ feed.entries << entry
25
25
  end
26
26
 
27
27
  puts feed.to_xml
@@ -6,7 +6,7 @@ module Xommelier
6
6
  attribute :term
7
7
 
8
8
  may do
9
- attribute :scheme, type: URI
9
+ attribute :scheme, type: Uri
10
10
  attribute :label
11
11
  end
12
12
  end
@@ -21,7 +21,7 @@ module Xommelier
21
21
  element :author, type: Person
22
22
  element :category, type: Category
23
23
  element :contributor, type: Person
24
- element :link
24
+ element :link, type: Link
25
25
  end
26
26
  end
27
27
  end
@@ -4,7 +4,7 @@ module Xommelier
4
4
  module Atom
5
5
  class Generator < Xml::Element
6
6
  may do
7
- attribute :uri, type: URI
7
+ attribute :uri, type: Uri
8
8
  attribute :version
9
9
  end
10
10
  end
@@ -3,7 +3,7 @@ require 'xommelier/atom'
3
3
  module Xommelier
4
4
  module Atom
5
5
  class Link < Xml::Element
6
- attribute :href, type: URI
6
+ attribute :href, type: Uri
7
7
 
8
8
  may do
9
9
  attribute :rel#, type: Enum(:alternate, :related, :self, :enclosure, :via)
@@ -7,7 +7,7 @@ module Xommelier
7
7
 
8
8
  may do
9
9
  element :email
10
- element :uri, type: URI
10
+ element :uri, type: Uri
11
11
  end
12
12
  end
13
13
  end
@@ -0,0 +1,38 @@
1
+ require 'xommelier/atom'
2
+
3
+ module Xommelier
4
+ module Atom
5
+ module Thread
6
+ include Xommelier::Xml
7
+
8
+ xmlns 'http://purl.org/syndication/thread/1.0', as: :thr
9
+
10
+ class InReplyTo < Xml::Element
11
+ element_name 'in-reply-to'
12
+ #method_name :in_reply_to
13
+
14
+ attribute :href
15
+
16
+ may do
17
+ attribute :href, type: Uri
18
+ attribute :type, type: String
19
+ attribute :source
20
+ end
21
+ end
22
+ end
23
+
24
+ # Extends Atom elements
25
+ class Entry
26
+ may do
27
+ element :in_reply_to, ns: Thread.xmlns
28
+ element :total, type: Integer, ns: Thread.xmlns
29
+ end
30
+ end
31
+
32
+ class Link
33
+ may do
34
+ attribute :count, type: Integer, ns: Thread.xmlns
35
+ end
36
+ end
37
+ end
38
+ end
@@ -5,7 +5,9 @@ module Xommelier
5
5
  include Xommelier::Xml
6
6
 
7
7
  xmlns 'http://www.w3.org/2005/Atom', as: :atom
8
+ #roots :Feed, :Entry
8
9
 
10
+ # Elements
9
11
  autoload :Link, 'xommelier/atom/link'
10
12
  autoload :Person, 'xommelier/atom/person'
11
13
  autoload :Category, 'xommelier/atom/category'
@@ -14,25 +16,7 @@ module Xommelier
14
16
  autoload :Feed, 'xommelier/atom/feed'
15
17
  autoload :Entry, 'xommelier/atom/entry'
16
18
 
17
- #namespace 'http://purl.org/syndication/thread/1.0', as: :thr do
18
- #element :in_reply_to, as: 'in-reply-to' do
19
- #attribute :ref
20
-
21
- #may do
22
- #attribute :href, type: Uri
23
- #attribute :type, type: String
24
- #attribute :source
25
- #end
26
- #end
27
-
28
- #ns.atom.element :entry do
29
- #element :in_reply_to, ns: ns.thr
30
- #element :total, type: Integer, ns: ns.thr
31
- #end
32
-
33
- #ns.atom.element :link, values: [:replies] do
34
- #ns.thr.attribute :count, type: Integer
35
- #end
36
- #end
19
+ # Extensions
20
+ autoload :Thread, 'xommelier/atom/thread'
37
21
  end
38
22
  end
@@ -1,4 +1,4 @@
1
- class Integer
1
+ class Numeric
2
2
  def self.from_xommelier(value)
3
3
  return nil if value.blank?
4
4
  begin
@@ -0,0 +1,9 @@
1
+ class Symbol
2
+ def self.from_xommelier(value)
3
+ value.to_sym
4
+ end
5
+
6
+ def to_xommelier
7
+ to_s
8
+ end
9
+ end
@@ -1,12 +1,18 @@
1
+ require 'xommelier/core_ext/string'
1
2
  require 'uri'
2
3
 
3
4
  class URI::Generic
4
- def self.from_xommelier(value)
5
- URI.parse value
6
- end
7
-
8
5
  def to_xommelier
9
6
  to_s
10
7
  end
11
8
  end
12
- Uri = String
9
+
10
+ class Uri < String
11
+ def self.from_xommelier(value)
12
+ if value.is_a?(URI::Generic)
13
+ value
14
+ else
15
+ URI.parse(value)
16
+ end
17
+ end
18
+ end
@@ -3,7 +3,8 @@ require 'xommelier'
3
3
  require 'xommelier/core_ext/boolean'
4
4
  require 'xommelier/core_ext/date'
5
5
  require 'xommelier/core_ext/float'
6
- require 'xommelier/core_ext/integer'
6
+ require 'xommelier/core_ext/numeric'
7
7
  require 'xommelier/core_ext/string'
8
+ require 'xommelier/core_ext/symbol'
8
9
  require 'xommelier/core_ext/time'
9
10
  require 'xommelier/core_ext/uri'
@@ -1,3 +1,3 @@
1
1
  module Xommelier
2
- VERSION = '0.0.1'
2
+ VERSION = '0.0.2'
3
3
  end
@@ -10,6 +10,7 @@ module Xommelier
10
10
  # Defines namespace used in formats
11
11
  def xmlns(uri = nil, options = {}, &block)
12
12
  if uri
13
+ options[:module] ||= self
13
14
  instance_variable_set :@_xmlns, Xommelier::Xml::Namespace.new(uri, options, &block)
14
15
  end
15
16
  instance_variable_get :@_xmlns
@@ -0,0 +1,148 @@
1
+ require 'xommelier/xml/element'
2
+ require 'active_support/concern'
3
+ require 'nokogiri'
4
+
5
+ module Xommelier
6
+ module Xml
7
+ class Element
8
+ module Serialization
9
+ extend ActiveSupport::Concern
10
+
11
+ SERIALIZATION_OPTIONS = {
12
+ encoding: 'utf-8'
13
+ }
14
+
15
+ module ClassMethods
16
+ def from_xml(xml, options = {})
17
+ new.tap do |doc|
18
+ doc.options = options
19
+ doc.from_xml(xml, options)
20
+ end
21
+ end
22
+ alias_method :parse, :from_xml
23
+ alias_method :from_xommelier, :from_xml
24
+
25
+ def element_xpath(xmldoc = nil, name = nil)
26
+ "#{xmlns_xpath(xmldoc)}:#{name || element_name}"
27
+ end
28
+
29
+ def xmlns_xpath(xml_document = nil)
30
+ if xml_document
31
+ xml_document.namespaces.key(xmlns.uri)
32
+ else
33
+ xmlns.as
34
+ end
35
+ end
36
+ end
37
+
38
+ def from_xml(xml, options = {})
39
+ case xml
40
+ when IO, String
41
+ xml = Nokogiri::XML(xml)
42
+ end
43
+ @_xml_node = options.delete(:node) { xml.at_xpath(element_xpath(xml.document, element_name)) }
44
+
45
+ if text? && @_xml_node.text?
46
+ self.text = @_xml_node.text
47
+ end
48
+
49
+ self.class.attributes.each do |name, options|
50
+ send(name, @_xml_node[name])
51
+ end
52
+
53
+ self.class.elements.each do |name, options|
54
+ deserialize_element(name, options)
55
+ end
56
+ end
57
+ alias_method :from_xommelier, :from_xml
58
+
59
+ def to_xml(options = {})
60
+ options = SERIALIZATION_OPTIONS.merge(options)
61
+ element_name = options.delete(:element_name) { self.element_name }
62
+ if options[:builder] # Non-root element
63
+ builder = options.delete(:builder)
64
+ attribute_values = {}
65
+ else # Root element
66
+ builder = Nokogiri::XML::Builder.new(options)
67
+ attribute_values = {xmlns: xmlns.to_s}
68
+ end
69
+ attributes.each do |name, value|
70
+ serialize_attribute(name, value, attribute_values)
71
+ end
72
+ builder.send(element_name, attribute_values) do |xml|
73
+ elements.each do |name, value|
74
+ serialize_element(name, value, xml)
75
+ end
76
+ if respond_to?(:text)
77
+ xml.text @text
78
+ end
79
+ end
80
+ builder.to_xml
81
+ end
82
+ alias_method :to_xommelier, :to_xml
83
+
84
+ protected
85
+
86
+ def element_xpath(xmldoc = self.xml_document, name = nil)
87
+ self.class.element_xpath(xmldoc, name)
88
+ end
89
+
90
+ def xml_document
91
+ @_xml_node.document
92
+ end
93
+
94
+ def xmlns_xpath(xml_document = self.xml_document)
95
+ self.class.xmlns_xpath(xml_document)
96
+ end
97
+
98
+ def serialize_attribute(name, value, attributes)
99
+ attributes[name] = value.to_xommelier
100
+ end
101
+
102
+ def deserialize_element(name, options = nil)
103
+ options ||= self.element_options(name)
104
+ type = options[:type]
105
+ xpath = if type < Xommelier::Xml::Element
106
+ type.element_xpath(xml_document, name)
107
+ else
108
+ element_xpath(xml_document, name)
109
+ end
110
+ nodes = @_xml_node.xpath("./#{xpath}")
111
+ if nodes.any?
112
+ case options[:count]
113
+ when :any, :many
114
+ children = nodes.map { |node| typecast_element(type, node, options) }
115
+ send options[:plural], children
116
+ else
117
+ send(name, typecast_element(type, nodes[0], options))
118
+ end
119
+ end
120
+ end
121
+
122
+ def typecast_element(type, node, options)
123
+ if type < Xommelier::Xml::Element
124
+ type.from_xommelier(xml_document, options.merge(node: node))
125
+ else
126
+ type.from_xommelier(node.text)
127
+ end
128
+ end
129
+
130
+ def serialize_element(name, value, xml, options = nil)
131
+ options ||= self.element_options(name)
132
+ case options[:count]
133
+ when :any, :many
134
+ single_element = options.merge(count: :one)
135
+ value.each { |item| serialize_element(name, item, xml, single_element) }
136
+ else
137
+ case value
138
+ when Xommelier::Xml::Element
139
+ value.to_xommelier(builder: xml, element_name: name)
140
+ else
141
+ xml.send(name) { xml.text value.to_xommelier }
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end