xmlparsable 1.1
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.
- data/README.md +80 -0
- data/Rakefile +19 -0
- data/lib/xmlparsable/elements/abstract.rb +21 -0
- data/lib/xmlparsable/elements/date.rb +25 -0
- data/lib/xmlparsable/elements/list.rb +31 -0
- data/lib/xmlparsable/elements/record.rb +48 -0
- data/lib/xmlparsable/elements/string.rb +23 -0
- data/lib/xmlparsable/elements/text.rb +34 -0
- data/lib/xmlparsable/elements/time.rb +25 -0
- data/lib/xmlparsable/elements/xml.rb +79 -0
- data/lib/xmlparsable/elements.rb +12 -0
- data/lib/xmlparsable/parser.rb +42 -0
- data/lib/xmlparsable.rb +74 -0
- data/spec/examples/record.example +229 -0
- data/spec/spec_helper.rb +13 -0
- metadata +60 -0
data/README.md
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
# XmlParsable
|
2
|
+
|
3
|
+
XmlParsable is a simple means to declare how an XML grammar maps to Ruby objects. It does not
|
4
|
+
perform validation of the input, and it is only able to describe fairly simple structures. It
|
5
|
+
relies on LibXML::XML::SaxParser.
|
6
|
+
|
7
|
+
1. Declare XML grammar
|
8
|
+
|
9
|
+
require "xmlparsable"
|
10
|
+
|
11
|
+
# Needed only for using the "collection" declaration or
|
12
|
+
# implement your own String#pluralize, String#singularize
|
13
|
+
require "active_support/core_ext"
|
14
|
+
|
15
|
+
class AddressBook
|
16
|
+
include XmlParsable
|
17
|
+
|
18
|
+
class Contact
|
19
|
+
include XmlParsable
|
20
|
+
element :givenname
|
21
|
+
element :familyname
|
22
|
+
element :phone
|
23
|
+
element :email
|
24
|
+
element :spouse, Contact
|
25
|
+
collection :children, Contact
|
26
|
+
element :birthdate, DateElement
|
27
|
+
element :updated, TimeElement
|
28
|
+
end
|
29
|
+
|
30
|
+
collection :contact, Contact
|
31
|
+
end
|
32
|
+
|
33
|
+
2. Parse input XML from string, IO, or File
|
34
|
+
|
35
|
+
book = AddressBook.parse(<<-XML)
|
36
|
+
<contacts>
|
37
|
+
<contact>
|
38
|
+
<familyname>Kim</familyname>
|
39
|
+
<givenname>Jong-il</givenname>
|
40
|
+
<birthdate>1942-02-16</birthdate>
|
41
|
+
<updated>2010-01-01T12:45:30</updated>
|
42
|
+
<children>
|
43
|
+
<child><givenname>Sul-song</givenname></child>
|
44
|
+
<child><givenname>Jong-chul</givenname></child>
|
45
|
+
<child><givenname>Jong-un</givenname></child>
|
46
|
+
<child>
|
47
|
+
<givenname>Jong-nam</givenname>
|
48
|
+
<spouse>
|
49
|
+
<givenname>Jong-hui</givenname>
|
50
|
+
<familyname>Shin</familyname>
|
51
|
+
</spouse>
|
52
|
+
</child>
|
53
|
+
</children>
|
54
|
+
</contact>
|
55
|
+
|
56
|
+
<contact>
|
57
|
+
<givenname>Vladimir</givenname>
|
58
|
+
<familyname>Putin</familyname>
|
59
|
+
<email>vlad@gmail.com</email>
|
60
|
+
<spouse>
|
61
|
+
<givenname>Lyudmila</givenname>
|
62
|
+
<familyname>Putina</familyname>
|
63
|
+
</spouse>
|
64
|
+
</contact>
|
65
|
+
</contacts>
|
66
|
+
XML
|
67
|
+
|
68
|
+
3. Iterate parsed data
|
69
|
+
|
70
|
+
kim, vlad = book.contacts
|
71
|
+
kim.givenname #=> "Kim"
|
72
|
+
kim.birthdate #=> #<Date: 4860813/2,0,2299161>
|
73
|
+
kim.updated #=> Fri Jan 01 12:45:30 2010
|
74
|
+
|
75
|
+
sulsong, jongchul, jongun, jongnam = kim.children
|
76
|
+
jongnam.spouse.givenname #=> "Jong-hui"
|
77
|
+
jongnam.spouse.familyname #=> "Shin"
|
78
|
+
|
79
|
+
vlad.email #=> "vlad@gmail.com"
|
80
|
+
vlad.spouse #=> #<AddressBook::Contact @givenname="Lyudmila" @familyname="Putina">
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
begin
|
2
|
+
require "rspec/core/rake_task"
|
3
|
+
|
4
|
+
RSpec::Core::RakeTask.new do |t|
|
5
|
+
t.verbose = false
|
6
|
+
t.pattern = "spec/examples/**/*.example"
|
7
|
+
t.rspec_opts = ["--color", "--format=p"]
|
8
|
+
end
|
9
|
+
rescue LoadError
|
10
|
+
require "spec/rake/spectask"
|
11
|
+
|
12
|
+
Spec::Rake::SpecTask.new do |t|
|
13
|
+
t.pattern = "spec/examples/**/*.example"
|
14
|
+
t.spec_opts << "--color"
|
15
|
+
t.spec_opts << "--format=p"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
task :default => :spec
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "date"
|
2
|
+
|
3
|
+
module XmlParsable
|
4
|
+
module Elements
|
5
|
+
#
|
6
|
+
# Parses content into a ruby Date value
|
7
|
+
#
|
8
|
+
class DateElement < AbstractElement
|
9
|
+
attr_reader :name
|
10
|
+
|
11
|
+
def initialize(name, attributes, parent, *arguments)
|
12
|
+
@name, @parent, @string = name, parent, ""
|
13
|
+
end
|
14
|
+
|
15
|
+
def read(text)
|
16
|
+
@string << text
|
17
|
+
end
|
18
|
+
|
19
|
+
def finalize
|
20
|
+
stripped = @string.strip
|
21
|
+
@parent.closed(self, Date.parse(stripped)) if stripped != ""
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module XmlParsable
|
2
|
+
module Elements
|
3
|
+
|
4
|
+
class ListElement < AbstractElement
|
5
|
+
attr_reader :name
|
6
|
+
|
7
|
+
def initialize(name, attributes, parent, *arguments)
|
8
|
+
@name, @parent, @elements = name, parent, []
|
9
|
+
|
10
|
+
@cname = arguments.shift.to_s
|
11
|
+
@cparser = arguments.shift
|
12
|
+
@cargs = arguments
|
13
|
+
end
|
14
|
+
|
15
|
+
def open(name, attributes)
|
16
|
+
if name == @cname
|
17
|
+
@cparser.parsable.new(name, attributes, self, *@cargs)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def closed(element, value)
|
22
|
+
@elements << value
|
23
|
+
end
|
24
|
+
|
25
|
+
def finalize
|
26
|
+
@parent.closed(self, @elements)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module XmlParsable
|
2
|
+
module Elements
|
3
|
+
class RecordElement < AbstractElement
|
4
|
+
attr_reader :name, :attributes, :parent, :target
|
5
|
+
|
6
|
+
def initialize(name, attributes, parent, target, parsers, *arguments)
|
7
|
+
@name = name
|
8
|
+
@attributes = attributes
|
9
|
+
@parent = parent
|
10
|
+
@target = target
|
11
|
+
@parsers = parsers
|
12
|
+
@elements = Hash.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def open(name, attributes)
|
16
|
+
parser, *arguments = @parsers[name]
|
17
|
+
if parser
|
18
|
+
parser.new(name, attributes, self, *arguments)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def closed(element, value)
|
23
|
+
parser, *arguments = @parsers[element.name]
|
24
|
+
if arguments.include?(:repeated)
|
25
|
+
(@elements[element.name] ||= []) << value
|
26
|
+
else
|
27
|
+
@elements[element.name] = value
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def finalize
|
32
|
+
@elements.each{|name, value| @target.instance_variable_set("@#{name}", value) }
|
33
|
+
@parent.closed(self, @target) if @parent
|
34
|
+
end
|
35
|
+
|
36
|
+
class Proxy
|
37
|
+
def initialize(base, parsers)
|
38
|
+
@base, @parsers = base, parsers
|
39
|
+
end
|
40
|
+
|
41
|
+
def new(name, attributes, parent, *arguments)
|
42
|
+
RecordElement.new(name, attributes, parent, @base.new, @parsers)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module XmlParsable
|
2
|
+
module Elements
|
3
|
+
#
|
4
|
+
# Parses content into a ruby String value. This discards comments and children
|
5
|
+
# elements so "abc <!-- foo --> def <b>whatever</b>" is read as "abc def"
|
6
|
+
#
|
7
|
+
class StringElement < AbstractElement
|
8
|
+
attr_reader :name
|
9
|
+
|
10
|
+
def initialize(name, attributes, parent, *arguments)
|
11
|
+
@name, @parent, @string = name, parent, ""
|
12
|
+
end
|
13
|
+
|
14
|
+
def read(text)
|
15
|
+
@string << text
|
16
|
+
end
|
17
|
+
|
18
|
+
def finalize
|
19
|
+
@parent.closed(self, @string)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module XmlParsable
|
2
|
+
module Elements
|
3
|
+
#
|
4
|
+
# Parses content into a ruby String value. Discards comments and flattens children
|
5
|
+
# elements so "abc def <b>what<!-- foo -->ever</b>" is read as "abc def whatever"
|
6
|
+
#
|
7
|
+
class TextElement < XmlElement::Node
|
8
|
+
attr_reader :name, :attributes, :parent
|
9
|
+
|
10
|
+
def initialize(name, attributes, parent, *arguments)
|
11
|
+
@name, @attributes, @parent, @text = name, attributes, parent, ""
|
12
|
+
end
|
13
|
+
|
14
|
+
def open(name, attributes)
|
15
|
+
TextElement.new(name, attributes, self)
|
16
|
+
end
|
17
|
+
|
18
|
+
def read(text)
|
19
|
+
@text << text
|
20
|
+
end
|
21
|
+
|
22
|
+
def comment(text)
|
23
|
+
end
|
24
|
+
|
25
|
+
def closed(element, value)
|
26
|
+
@text << value
|
27
|
+
end
|
28
|
+
|
29
|
+
def finalize
|
30
|
+
@parent.closed(self, @text)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "time"
|
2
|
+
|
3
|
+
module XmlParsable
|
4
|
+
module Elements
|
5
|
+
#
|
6
|
+
# Parses content into a ruby Time value
|
7
|
+
#
|
8
|
+
class TimeElement < AbstractElement
|
9
|
+
attr_reader :name
|
10
|
+
|
11
|
+
def initialize(name, attributes, parent, *arguments)
|
12
|
+
@name, @parent, @string = name, parent, ""
|
13
|
+
end
|
14
|
+
|
15
|
+
def read(text)
|
16
|
+
@string << text
|
17
|
+
end
|
18
|
+
|
19
|
+
def finalize
|
20
|
+
stripped = @string.strip
|
21
|
+
@parent.closed(self, Time.parse(stripped)) if stripped != ""
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module XmlParsable
|
2
|
+
module Elements
|
3
|
+
#
|
4
|
+
# Parses content into a generic tree structure, consisting of three
|
5
|
+
# classes -- Node, Text, and Comment.
|
6
|
+
#
|
7
|
+
class XmlElement
|
8
|
+
|
9
|
+
def self.parsable
|
10
|
+
Node
|
11
|
+
end
|
12
|
+
|
13
|
+
class Node < AbstractElement
|
14
|
+
attr_reader :name, :attributes, :parent, :children
|
15
|
+
|
16
|
+
def initialize(name, attributes, parent, *arguments)
|
17
|
+
@parent, @name, @attributes, @children = parent, name, attributes, Array.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def open(name, attributes)
|
21
|
+
Node.new(name, attributes, self)
|
22
|
+
end
|
23
|
+
|
24
|
+
def read(text)
|
25
|
+
@children << Text.new(text, self)
|
26
|
+
end
|
27
|
+
|
28
|
+
def comment(text)
|
29
|
+
@children << Comment.new(text, self)
|
30
|
+
end
|
31
|
+
|
32
|
+
def closed(element, value)
|
33
|
+
@children << value
|
34
|
+
end
|
35
|
+
|
36
|
+
def finalize
|
37
|
+
@parent.closed(self, self)
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_s(buffer = "")
|
41
|
+
buffer << "<#{@name}"
|
42
|
+
@attributes.each{|name, value| buffer << %[ #{name}='#{value}'] }
|
43
|
+
|
44
|
+
if @children.empty?
|
45
|
+
buffer << "/>"
|
46
|
+
else
|
47
|
+
buffer << ">"
|
48
|
+
@children.each{|child| child.to_s(buffer) }
|
49
|
+
buffer << "</#{@name}>"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class Text
|
55
|
+
attr_reader :text, :parent
|
56
|
+
|
57
|
+
def initialize(text, parent)
|
58
|
+
@text, @parent = text, parent
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_s(buffer = "")
|
62
|
+
buffer << @text
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class Comment
|
67
|
+
attr_reader :text, :parent
|
68
|
+
|
69
|
+
def initialize(text, parent)
|
70
|
+
@text, @parent = text, parent
|
71
|
+
end
|
72
|
+
|
73
|
+
def to_s(buffer = "")
|
74
|
+
buffer << "<!--" << @text << "-->"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module XmlParsable
|
2
|
+
module Elements
|
3
|
+
autoload :AbstractElement, "xmlparsable/elements/abstract"
|
4
|
+
autoload :DateElement, "xmlparsable/elements/date"
|
5
|
+
autoload :ListElement, "xmlparsable/elements/list"
|
6
|
+
autoload :RecordElement, "xmlparsable/elements/record"
|
7
|
+
autoload :StringElement, "xmlparsable/elements/string"
|
8
|
+
autoload :TextElement, "xmlparsable/elements/text"
|
9
|
+
autoload :TimeElement, "xmlparsable/elements/time"
|
10
|
+
autoload :XmlElement, "xmlparsable/elements/xml"
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module XmlParsable
|
2
|
+
class Parser
|
3
|
+
attr_reader :documents
|
4
|
+
|
5
|
+
def initialize(constructed = nil, &constructor)
|
6
|
+
@documents = Array.new
|
7
|
+
@constructor = constructor || lambda { constructed }
|
8
|
+
end
|
9
|
+
|
10
|
+
def on_start_document
|
11
|
+
@stack = [@constructor.call]
|
12
|
+
end
|
13
|
+
|
14
|
+
def on_end_document
|
15
|
+
@documents << @stack.shift.finalize
|
16
|
+
end
|
17
|
+
|
18
|
+
def on_characters(text)
|
19
|
+
@stack.first.read(text)
|
20
|
+
end
|
21
|
+
|
22
|
+
def on_comment(comment)
|
23
|
+
@stack.first.comment(comment)
|
24
|
+
end
|
25
|
+
|
26
|
+
def on_start_element_ns(name, attributes, prefix, uri, namespaces)
|
27
|
+
@stack.unshift(@stack.first.open(name, attributes) || StubElement)
|
28
|
+
end
|
29
|
+
|
30
|
+
def on_end_element_ns(name, prefix, uri)
|
31
|
+
@stack.shift.finalize
|
32
|
+
end
|
33
|
+
|
34
|
+
module StubElement
|
35
|
+
def self.open(name, attributes) self end
|
36
|
+
def self.close(element, value) end
|
37
|
+
def self.comment(text) end
|
38
|
+
def self.read(text) end
|
39
|
+
def self.finalize; end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/xmlparsable.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
require "libxml"
|
2
|
+
|
3
|
+
module XmlParsable
|
4
|
+
|
5
|
+
autoload :Parser, "xmlparsable/parser"
|
6
|
+
autoload :Elements, "xmlparsable/elements"
|
7
|
+
|
8
|
+
def self.parser(input)
|
9
|
+
case input
|
10
|
+
when String then LibXML::XML::SaxParser.string(input)
|
11
|
+
when File then LibXML::XML::SaxParser.file(input)
|
12
|
+
when IO then LibXML::XML::SaxParser.io(input)
|
13
|
+
else raise ArgumentError, "argument must be a String, File, or IO"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.included(base)
|
18
|
+
base.extend(ClassMethods)
|
19
|
+
base.__send__(:include, Elements)
|
20
|
+
end
|
21
|
+
|
22
|
+
module ClassMethods
|
23
|
+
def element(name, *arguments)
|
24
|
+
element ||= arguments.pop.delete(:class) if arguments.last.is_a?(Hash)
|
25
|
+
element ||= arguments.shift if arguments.first.is_a?(Class)
|
26
|
+
element ||= Elements::StringElement
|
27
|
+
|
28
|
+
(@parsers ||= {})[name.to_s] = [element.parsable, *arguments]
|
29
|
+
|
30
|
+
define_method(name) { instance_variable_get("@#{name}") }
|
31
|
+
end
|
32
|
+
|
33
|
+
def repeated(name, *arguments)
|
34
|
+
element ||= arguments.pop.delete(:class) if arguments.last.is_a?(Hash)
|
35
|
+
element ||= arguments.shift if arguments.first.is_a?(Class)
|
36
|
+
element ||= Elements::StringElement
|
37
|
+
|
38
|
+
(@parsers ||= {})[name.to_s.singularize] = [element.parsable, *arguments.push(:repeated)]
|
39
|
+
|
40
|
+
define_method(name) { instance_variable_get("@#{name.to_s.singularize}") }
|
41
|
+
end
|
42
|
+
|
43
|
+
def collection(name, *arguments)
|
44
|
+
item ||= arguments.pop.delete(:class) if arguments.last.is_a?(Hash)
|
45
|
+
item ||= arguments.shift if arguments.first.is_a?(Class)
|
46
|
+
item ||= Elements::StringElement
|
47
|
+
|
48
|
+
if arguments.empty?
|
49
|
+
single = name.to_s.singularize
|
50
|
+
plural = name.to_s.pluralize
|
51
|
+
else
|
52
|
+
plural = name
|
53
|
+
single = arguments.shift
|
54
|
+
end
|
55
|
+
|
56
|
+
element(plural, Elements::ListElement, single, item)
|
57
|
+
end
|
58
|
+
|
59
|
+
def parse(input)
|
60
|
+
root = XmlParsable::Elements::RecordElement.new(nil, nil, nil, new, @parsers)
|
61
|
+
stack = XmlParsable::Parser.new(root)
|
62
|
+
parser = XmlParsable.parser(input)
|
63
|
+
|
64
|
+
parser.callbacks = stack
|
65
|
+
parser.parse
|
66
|
+
root.target
|
67
|
+
end
|
68
|
+
|
69
|
+
def parsable
|
70
|
+
Elements::RecordElement::Proxy.new(self, @parsers)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
@@ -0,0 +1,229 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe XmlParsable::Elements::RecordElement do
|
4
|
+
|
5
|
+
class Record
|
6
|
+
include XmlParsable
|
7
|
+
|
8
|
+
element :date, DateElement
|
9
|
+
element :record, self
|
10
|
+
element :string, StringElement
|
11
|
+
element :text, TextElement
|
12
|
+
element :time, TimeElement
|
13
|
+
element :xml, XmlElement
|
14
|
+
|
15
|
+
collection :kids, self
|
16
|
+
collection :list, :item
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "date" do
|
20
|
+
it "parses date into a ruby Date" do
|
21
|
+
Record.parse("<date>#{Date.today}</date>").date.should == Date.today
|
22
|
+
end
|
23
|
+
|
24
|
+
it "parses missing date as nil" do
|
25
|
+
Record.parse("<date><!-- nope --></date>").date.should == nil
|
26
|
+
end
|
27
|
+
|
28
|
+
it "ignores children" do
|
29
|
+
Record.parse("<date><x>2010-01-01</x></date>").date.should == nil
|
30
|
+
end
|
31
|
+
|
32
|
+
it "ignores comments" do
|
33
|
+
Record.parse("<date>#{Date.today} <!-- that's today --></date>").date.should == Date.today
|
34
|
+
end
|
35
|
+
|
36
|
+
it "ignores whitespace" do
|
37
|
+
Record.parse("<date>\n #{Date.today}\n </date>").date.should == Date.today
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "list of strings" do
|
42
|
+
it "parses empty list into an empty Array" do
|
43
|
+
Record.parse("<list/>").list.should == []
|
44
|
+
end
|
45
|
+
|
46
|
+
it "parses non-empty list into an Array" do
|
47
|
+
Record.parse("<list><item>a</item><item>b</item></list>").list.should == ["a", "b"]
|
48
|
+
end
|
49
|
+
|
50
|
+
it "ignores non-matching children" do
|
51
|
+
Record.parse("<list><fail/><item/><boat/><item/><item/></list>").list.should == ["", "", ""]
|
52
|
+
end
|
53
|
+
|
54
|
+
it "ignores comments" do
|
55
|
+
Record.parse("<list><!-- <item>x</item> --><item><!-- y --></item></list>").list.should == [""]
|
56
|
+
end
|
57
|
+
|
58
|
+
it "ignores whitespace between children" do
|
59
|
+
Record.parse(<<-XML).list.should == ["a", "b", "c"]
|
60
|
+
<list>
|
61
|
+
<item>a</item>
|
62
|
+
<item>b</item>
|
63
|
+
<item>c</item>
|
64
|
+
</list>
|
65
|
+
XML
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "list of records" do
|
70
|
+
it "parses empty list into an empty Array" do
|
71
|
+
Record.parse("<kids/>").kids.should == []
|
72
|
+
end
|
73
|
+
|
74
|
+
it "parses non-empty list into an array" do
|
75
|
+
x = Record.parse(<<-XML)
|
76
|
+
<kids>
|
77
|
+
<kid><text>first</text></kid>
|
78
|
+
<kid><text>second</text></kid>
|
79
|
+
<kid><text>third</text></kid>
|
80
|
+
</kids>
|
81
|
+
XML
|
82
|
+
|
83
|
+
x.kids.should have(3).items
|
84
|
+
x.kids[0].text.should == "first"
|
85
|
+
x.kids[1].text.should == "second"
|
86
|
+
x.kids[2].text.should == "third"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe "string" do
|
91
|
+
it "parses empty string" do
|
92
|
+
Record.parse("<string></string>").string.should == ""
|
93
|
+
end
|
94
|
+
|
95
|
+
it "parses non-empty string" do
|
96
|
+
Record.parse("<string>lo and behold</string>").string.should == "lo and behold"
|
97
|
+
end
|
98
|
+
|
99
|
+
it "ignores comments" do
|
100
|
+
Record.parse("<string>hi<!-- secret message --></string>").string.should == "hi"
|
101
|
+
end
|
102
|
+
|
103
|
+
it "ignores children" do
|
104
|
+
Record.parse("<string><b>bold</b>, <i>italic</i>, plain</string>").string.should == ", , plain"
|
105
|
+
end
|
106
|
+
|
107
|
+
it "does not ignore whitespace" do
|
108
|
+
Record.parse("<string> foob ar</string>").string.should == " foob ar"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe "text" do
|
113
|
+
it "parses text" do
|
114
|
+
Record.parse("<text>lo and behold</text>").text.should == "lo and behold"
|
115
|
+
end
|
116
|
+
|
117
|
+
it "ignores comments" do
|
118
|
+
Record.parse("<text>lo <!--and--> behold</text>").text.should == "lo behold"
|
119
|
+
end
|
120
|
+
|
121
|
+
it "strips away markup" do
|
122
|
+
Record.parse("<text>lo <span class='x'><i>and</i> behold</span></text>").text.should == "lo and behold"
|
123
|
+
end
|
124
|
+
|
125
|
+
it "does not ignore whitespace" do
|
126
|
+
Record.parse("<text> foob ar</text>").text.should == " foob ar"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe "time" do
|
131
|
+
it "parses time" do
|
132
|
+
time = Time.now
|
133
|
+
Record.parse("<time>#{time}</time>").time.to_i.should == time.to_i
|
134
|
+
end
|
135
|
+
|
136
|
+
it "ignores comments" do
|
137
|
+
Record.parse("<time><!-- #{Time.now} --></time>").time.should be_nil
|
138
|
+
end
|
139
|
+
|
140
|
+
it "ignores children" do
|
141
|
+
Record.parse("<time><b>#{Time.now}</b></time>").time.should be_nil
|
142
|
+
end
|
143
|
+
|
144
|
+
it "ignores whitespace" do
|
145
|
+
time = Time.now
|
146
|
+
Record.parse("<time> #{time} </time>").time.to_i.should == time.to_i
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
describe "record" do
|
151
|
+
it "parses date" do
|
152
|
+
x = Record.parse("<record><date>#{Date.today}</date></record>")
|
153
|
+
x.date.should be_nil
|
154
|
+
x.record.date.should == Date.today
|
155
|
+
end
|
156
|
+
|
157
|
+
it "parses list" do
|
158
|
+
x = Record.parse("<record><list/></record>")
|
159
|
+
x.list.should be_nil
|
160
|
+
x.record.list.should == []
|
161
|
+
end
|
162
|
+
|
163
|
+
it "parses string" do
|
164
|
+
x = Record.parse("<record><string>abc</string></record>")
|
165
|
+
x.string.should be_nil
|
166
|
+
x.record.string.should == "abc"
|
167
|
+
end
|
168
|
+
|
169
|
+
it "parses text" do
|
170
|
+
x = Record.parse("<record><text><b>big</b><i>little</i></text></record>")
|
171
|
+
x.text.should be_nil
|
172
|
+
x.record.text.should == "biglittle"
|
173
|
+
end
|
174
|
+
|
175
|
+
it "parses time" do
|
176
|
+
time = Time.now
|
177
|
+
Record.parse("<record><time>#{time}</time></record>").record.time.to_i.should == time.to_i
|
178
|
+
end
|
179
|
+
|
180
|
+
it "parses multiple elements" do
|
181
|
+
time = Time.now
|
182
|
+
x = Record.parse <<-XML
|
183
|
+
<record>
|
184
|
+
<date>#{Date.today}</date>
|
185
|
+
<list>
|
186
|
+
<item>a</item>
|
187
|
+
<item>b</item>
|
188
|
+
<item>c</item>
|
189
|
+
</list>
|
190
|
+
<string>xyz</string>
|
191
|
+
<text>
|
192
|
+
<ul>
|
193
|
+
<li>vim</li>
|
194
|
+
<li>emacs</li>
|
195
|
+
<li>ctags</li>
|
196
|
+
<li>lint</li>
|
197
|
+
<li>gdb</li>
|
198
|
+
</ul>
|
199
|
+
</text>
|
200
|
+
<time>#{time}</time>
|
201
|
+
</record>
|
202
|
+
XML
|
203
|
+
|
204
|
+
x.record.date.should == Date.today
|
205
|
+
x.record.list.should == ["a","b","c"]
|
206
|
+
x.record.string.should == "xyz"
|
207
|
+
x.record.text.strip.should == <<-DOC.strip
|
208
|
+
vim
|
209
|
+
emacs
|
210
|
+
ctags
|
211
|
+
lint
|
212
|
+
gdb
|
213
|
+
DOC
|
214
|
+
|
215
|
+
x.record.time.to_i.should == time.to_i
|
216
|
+
end
|
217
|
+
|
218
|
+
it "parses recursive records" do
|
219
|
+
x = Record.parse("<record><record><record><record></record></record></record></record>")
|
220
|
+
x.should be_a(Record)
|
221
|
+
x.record.should be_a(Record)
|
222
|
+
x.record.record.should be_a(Record)
|
223
|
+
x.record.record.record.should be_a(Record)
|
224
|
+
x.record.record.record.record.should be_a(Record)
|
225
|
+
x.record.record.record.record.record.should be_nil
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: xmlparsable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '1.1'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Kyle Putnam
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-01-14 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Simple declarative XML parsing
|
15
|
+
email: putnam.kyle@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- README.md
|
21
|
+
- Rakefile
|
22
|
+
- lib/xmlparsable/elements/abstract.rb
|
23
|
+
- lib/xmlparsable/elements/date.rb
|
24
|
+
- lib/xmlparsable/elements/list.rb
|
25
|
+
- lib/xmlparsable/elements/record.rb
|
26
|
+
- lib/xmlparsable/elements/string.rb
|
27
|
+
- lib/xmlparsable/elements/text.rb
|
28
|
+
- lib/xmlparsable/elements/time.rb
|
29
|
+
- lib/xmlparsable/elements/xml.rb
|
30
|
+
- lib/xmlparsable/elements.rb
|
31
|
+
- lib/xmlparsable/parser.rb
|
32
|
+
- lib/xmlparsable.rb
|
33
|
+
- spec/examples/record.example
|
34
|
+
- spec/spec_helper.rb
|
35
|
+
homepage: https://github.com/kputnam/xmlparsable
|
36
|
+
licenses: []
|
37
|
+
post_install_message:
|
38
|
+
rdoc_options: []
|
39
|
+
require_paths:
|
40
|
+
- lib
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
none: false
|
49
|
+
requirements:
|
50
|
+
- - ! '>='
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '0'
|
53
|
+
requirements: []
|
54
|
+
rubyforge_project:
|
55
|
+
rubygems_version: 1.8.24
|
56
|
+
signing_key:
|
57
|
+
specification_version: 3
|
58
|
+
summary: Simple declarative XML parsing
|
59
|
+
test_files:
|
60
|
+
- spec/examples/record.example
|