xmlparsable 1.4.2 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,36 @@
1
+ module XmlParsable
2
+ module Elements
3
+
4
+ class ArrayElement < AbstractElement
5
+ attr_reader :name
6
+
7
+ class Array < ::Array
8
+ attr_reader :xmlattrs
9
+ end
10
+
11
+ def initialize(name, attributes, parent, arguments)
12
+ @name, @parent, @elements = name, parent, Array.new
13
+
14
+ @item_name = arguments[:item_name].to_s
15
+ @item_type = arguments[:class]
16
+ @item_args = arguments[:item]
17
+ @elements.instance_variable_set(:@xmlattrs, attributes)
18
+ end
19
+
20
+ def open(name, attributes)
21
+ if name == @item_name
22
+ @item_type.parsable.new(name, attributes, self, @item_args)
23
+ end
24
+ end
25
+
26
+ def close(element, value)
27
+ @elements << value
28
+ end
29
+
30
+ def finalize
31
+ @parent.close(self, @elements)
32
+ end
33
+ end
34
+
35
+ end
36
+ end
@@ -10,10 +10,12 @@ module XmlParsable
10
10
  @target = target
11
11
  @parsers = parsers
12
12
  @elements = Hash.new
13
+ @target.instance_variable_set(:@xmlattrs, @attributes)
13
14
  end
14
15
 
15
16
  def open(name, attributes)
16
17
  parser, *arguments = @parsers[name]
18
+
17
19
  if parser
18
20
  parser.new(name, attributes, self, *arguments)
19
21
  end
@@ -21,15 +23,16 @@ module XmlParsable
21
23
 
22
24
  def close(element, value)
23
25
  parser, *arguments = @parsers[element.name]
26
+
24
27
  if arguments.include?(:repeated)
25
- (@elements[element.name] ||= []) << value
28
+ (@elements[element.name.to_sym] ||= []) << value
26
29
  else
27
- @elements[element.name] = value
30
+ @elements[element.name.to_sym] = value
28
31
  end
29
32
  end
30
33
 
31
34
  def finalize
32
- @elements.each{|name, value| @target.instance_variable_set("@#{name}", value) }
35
+ @target.instance_variable_set(:@xmlelems, @elements)
33
36
  @parent.close(self, @target) if @parent
34
37
  end
35
38
 
@@ -2,7 +2,7 @@ module XmlParsable
2
2
  module Elements
3
3
  autoload :AbstractElement, "xmlparsable/elements/abstract"
4
4
  autoload :DateElement, "xmlparsable/elements/date"
5
- autoload :ListElement, "xmlparsable/elements/list"
5
+ autoload :ArrayElement, "xmlparsable/elements/array"
6
6
  autoload :NumericElement, "xmlparsable/elements/numeric"
7
7
  autoload :RecordElement, "xmlparsable/elements/record"
8
8
  autoload :StringElement, "xmlparsable/elements/string"
data/lib/xmlparsable.rb CHANGED
@@ -20,50 +20,58 @@ module XmlParsable
20
20
  end
21
21
 
22
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
23
 
28
- # Look up the inheritance hierarchy to map element name to parser
29
- parent = superclass < XmlParsable ?
30
- superclass.instance_variable_get(:@parsers) : Hash.new
24
+ # element :name, StringElement, {options...}
25
+ # element :name, {:class => StringElement, options...}
26
+ def element(elem_name, *extras)
27
+ arguments = extras.pop if extras.last.is_a?(Hash)
28
+ arguments ||= Hash.new
31
29
 
32
- @parsers ||= Hash.new{|h,k| parent[k] }
33
- @parsers[name.to_s] = [element.parsable, *arguments]
34
-
35
- define_method(name) { instance_variable_get("@#{name}") }
36
- end
37
-
38
- def repeated(name, *arguments)
39
- element ||= arguments.pop.delete(:class) if arguments.last.is_a?(Hash)
40
- element ||= arguments.shift if arguments.first.is_a?(Class)
41
- element ||= Elements::StringElement
30
+ elem_type = extras.shift if extras.first.is_a?(Class)
31
+ elem_type ||= arguments.delete(:class)
32
+ elem_type ||= Elements::StringElement
33
+ raise ArgumentError, "extra arguments" unless extras.empty?
42
34
 
43
35
  # Look up the inheritance hierarchy to map element name to parser
44
36
  parent = superclass < XmlParsable ?
45
37
  superclass.instance_variable_get(:@parsers) : Hash.new
46
38
 
47
39
  @parsers ||= Hash.new{|h,k| parent[k] }
48
- @parsers[name.to_s.singularize] = [element.parsable, *arguments.push(:repeated)]
40
+ @parsers[elem_name.to_s] = [elem_type.parsable, arguments]
41
+
42
+ # Define accessor method (or don't, see if I care!)
43
+ attr_name = arguments.fetch(:accessor, elem_name)
49
44
 
50
- define_method(name) { instance_variable_get("@#{name.to_s.singularize}") }
45
+ if attr_name and not method_defined?(attr_name)
46
+ define_method(attr_name) { instance_variable_get(:@xmlelems)[elem_name] }
47
+ end
51
48
  end
52
49
 
53
- def collection(name, *arguments)
54
- item ||= arguments.pop.delete(:class) if arguments.last.is_a?(Hash)
55
- item ||= arguments.shift if arguments.first.is_a?(Class)
56
- item ||= Elements::StringElement
50
+ # collection :hive, BeeElement, {:item_name => :bee, array-options..., :item => {item-options...}}
51
+ # collection :hive, :bee, BeeElement, {array-options..., :item => {item-options...}}
52
+ # collection :hive, {array-options..., :item => {:class => BeeElement, item-options...}}
53
+ def collection(elem_name, *extras)
54
+ arguments = extras.pop if extras.last.is_a?(Hash)
55
+ arguments ||= Hash.new
56
+
57
+ arguments[:item] ||= Hash.new
58
+ arguments[:item_name] ||= extras.shift unless extras.first.is_a?(Class)
59
+ arguments[:item_name] ||= elem_name.to_s.singularize
60
+ arguments[:class] ||= extras.shift if extras.first.is_a?(Class)
61
+ arguments[:class] ||= Elements::StringElement
62
+ raise ArgumentError, "extra arguments" unless extras.empty?
63
+
64
+ element(elem_name, Elements::ArrayElement, arguments)
65
+ end
57
66
 
58
- if arguments.empty?
59
- single = name.to_s.singularize
60
- plural = name.to_s.pluralize
67
+ def repeated(elem_name, *extras)
68
+ if extras.last.is_a?(Hash)
69
+ extras.last[:repeated] = true
70
+ element(elem_name, *extras)
61
71
  else
62
- plural = name
63
- single = arguments.shift
72
+ extras.push(Hash[:repeated => true])
73
+ element(elem_name, *extras)
64
74
  end
65
-
66
- element(plural, Elements::ListElement, single, item)
67
75
  end
68
76
 
69
77
  def parse(input)
@@ -0,0 +1,89 @@
1
+ require "spec_helper"
2
+
3
+ describe XmlParsable, "accessors" do
4
+
5
+ let(:input) do
6
+ <<-XML
7
+ <root lang="en" charset="utf-8">
8
+ <size>500</size>
9
+ <wise>none</wise>
10
+ <secret>Ovaltine</secret>
11
+ </root>
12
+ XML
13
+ end
14
+
15
+ context "default" do
16
+ let(:parser) do
17
+ Class.new do
18
+ class Inner
19
+ attr_reader :xmlattrs
20
+ attr_reader :xmlelems
21
+
22
+ include XmlParsable
23
+ element :size
24
+ element :wise
25
+ end
26
+
27
+ include XmlParsable
28
+ element :root, Inner
29
+ end
30
+ end
31
+
32
+ it "stores attributes in `xmlattrs`" do
33
+ xml = parser.parse(input)
34
+ xml.root.xmlattrs.should == Hash["lang" => "en", "charset" => "utf-8"]
35
+ end
36
+
37
+ it "stores elements in `xmlelems`" do
38
+ xml = parser.parse(input)
39
+ xml.root.xmlelems[:size] = "500"
40
+ xml.root.xmlelems[:wise] = "none"
41
+ end
42
+
43
+ it "creates accessors for elements" do
44
+ xml = parser.parse(input)
45
+ xml.root.xmlelems[:size].should eql(xml.root.size)
46
+ xml.root.xmlelems[:wise].should eql(xml.root.wise)
47
+ end
48
+ end
49
+
50
+ context "overridden" do
51
+ let(:parser) do
52
+ Class.new do
53
+ class Inner
54
+ attr_reader :xmlelems
55
+
56
+ def size
57
+ "junk junk"
58
+ end
59
+
60
+ include XmlParsable
61
+ element :size
62
+ element :wise, accessor: :wisdom
63
+ element :secret, accessor: false
64
+ end
65
+
66
+ include XmlParsable
67
+ element :root, Inner
68
+ end
69
+ end
70
+
71
+ it "stores elements in `xmlelems`" do
72
+ xml = parser.parse(input)
73
+ xml.root.xmlelems[:size] == "500"
74
+ xml.root.xmlelems[:wise] == "none"
75
+ xml.root.xmlelems[:secret] == "Ovaltine"
76
+ end
77
+
78
+ it "doesn't create accessors for hidden elements" do
79
+ xml = parser.parse(input)
80
+ xml.root.should_not respond_to(:secret)
81
+ end
82
+
83
+ it "creates accessors for renamed elements" do
84
+ xml = parser.parse(input)
85
+ xml.root.xmlelems[:wise].should eql(xml.root.wisdom)
86
+ end
87
+ end
88
+
89
+ end
@@ -0,0 +1,110 @@
1
+ require "spec_helper"
2
+
3
+ describe XmlParsable::Elements::ArrayElement do
4
+
5
+ describe "declaration" do
6
+
7
+ it "fully specified" do
8
+ parser = Class.new do
9
+ sheep = Class.new do
10
+ include XmlParsable
11
+ element :name
12
+ element :bahh
13
+ end
14
+
15
+ include XmlParsable
16
+ collection :flock, :sheep, sheep
17
+ end
18
+
19
+ xml = parser.parse(<<-XML)
20
+ <flock>
21
+ <sheep><bahh>H*</bahh></sheep>
22
+ <sheep><bahh>HH</bahh></sheep>
23
+ <sheep><bahh>*H</bahh></sheep>
24
+ </flock>
25
+ XML
26
+
27
+ xml.flock[0].bahh.should == "H*"
28
+ xml.flock[1].bahh.should == "HH"
29
+ xml.flock[2].bahh.should == "*H"
30
+ end
31
+
32
+ specify "default child type is StringElement" do
33
+ parser = Class.new do
34
+ include XmlParsable
35
+ collection :contacts, :name
36
+ end
37
+
38
+ xml = parser.parse("<contacts><name>Green</name><name>White</name></contacts>")
39
+ xml.contacts.should == %w(Green White)
40
+ end
41
+
42
+ specify "default child name is singular collection name" do
43
+ parser = Class.new do
44
+ include XmlParsable
45
+ collection :bees, XmlParsable::Elements::NumericElement
46
+ end
47
+
48
+ xml = parser.parse("<bees><bee>1000</bee><bee>1001</bee></bees>")
49
+ xml.bees.should == [1000, 1001]
50
+ end
51
+ end
52
+
53
+ describe "parser" do
54
+ let(:parser) do
55
+ Class.new do
56
+ include XmlParsable
57
+ collection :list, :item
58
+ end
59
+ end
60
+
61
+ it "parses empty list into an empty Array" do
62
+ input = "<list/>"
63
+ expected = []
64
+
65
+ parser.parse(input).list.should == expected
66
+ end
67
+
68
+ it "parses attributes" do
69
+ input = '<list thang="yarn" milk="yawn"/>'
70
+ expected = Hash["thang" => "yarn", "milk" => "yawn"]
71
+
72
+ parser.parse(input).list.xmlattrs.should == expected
73
+ end
74
+
75
+ it "parses non-empty list into an Array" do
76
+ input = "<list><item>a</item><item>b</item></list>"
77
+ expected = ["a", "b"]
78
+
79
+ parser.parse(input).list.should == expected
80
+ end
81
+
82
+ it "ignores non-matching children" do
83
+ input = "<list><fail/><item/><boat/><item/><item/></list>"
84
+ expected = ["", "", ""]
85
+
86
+ parser.parse(input).list.should == expected
87
+ end
88
+
89
+ it "ignores comments" do
90
+ input = "<list><!-- <item>x</item> --><item><!-- y --></item></list>"
91
+ expected = [""]
92
+
93
+ parser.parse(input).list.should == expected
94
+ end
95
+
96
+ it "ignores whitespace between children" do
97
+ input = <<-XML
98
+ <list>
99
+ <item>a</item>
100
+ <item>b</item>
101
+ <item>c</item>
102
+ </list>
103
+ XML
104
+
105
+ expected = ["a", "b", "c"]
106
+ parser.parse(input).list.should == expected
107
+ end
108
+ end
109
+
110
+ end
@@ -0,0 +1,44 @@
1
+ require "spec_helper"
2
+
3
+ describe XmlParsable::Elements::DateElement do
4
+
5
+ describe "parser" do
6
+ let(:parser) do
7
+ Class.new do
8
+ include XmlParsable
9
+ element :date, XmlParsable::Elements::DateElement
10
+ end
11
+ end
12
+
13
+ it "parses date into a ruby Date" do
14
+ expected = Date.today
15
+ input = "<date>#{expected}</date>"
16
+ parser.parse(input).date.should == expected
17
+ end
18
+
19
+ it "parses missing date as nil" do
20
+ expected = nil
21
+ input = "<date><!-- nope --></date>"
22
+ parser.parse(input).date.should == expected
23
+ end
24
+
25
+ it "ignores children" do
26
+ expected = nil
27
+ input = "<date><x>2010-01-01</x></date>"
28
+ parser.parse(input).date.should == expected
29
+ end
30
+
31
+ it "ignores comments" do
32
+ expected = Date.today
33
+ input = "<date>#{expected} <!-- that's today --></date>"
34
+ parser.parse(input).date.should == expected
35
+ end
36
+
37
+ it "ignores whitespace" do
38
+ expected = Date.today
39
+ input = "<date>\n #{Date.today}\n </date>"
40
+ parser.parse(input).date.should == expected
41
+ end
42
+ end
43
+
44
+ end
@@ -0,0 +1,60 @@
1
+ require "spec_helper"
2
+
3
+ describe XmlParsable::Elements::StringElement do
4
+
5
+ describe "parser" do
6
+ let(:parser) do
7
+ Class.new do
8
+ include XmlParsable
9
+ element :number, XmlParsable::Elements::NumericElement
10
+ end
11
+ end
12
+
13
+ it "parses integers" do
14
+ expected = rand(1_000)
15
+ input = "<number>#{expected}</number>"
16
+ parser.parse(input).number.should == expected
17
+ end
18
+
19
+ it "parses hexadecimal" do
20
+ expected = rand(1_000)
21
+ input = "<number>0x#{expected.to_s(16)}</number>"
22
+ parser.parse(input).number.should == expected
23
+ end
24
+
25
+ it "parser octal" do
26
+ expected = rand(1_000)
27
+ input = "<number>0#{expected.to_s(8)}</number>"
28
+ parser.parse(input).number.should == expected
29
+ end
30
+
31
+ it "parses binary" do
32
+ expected = rand(1_000)
33
+ input = "<number>0b#{expected.to_s(2)}</number>"
34
+ parser.parse(input).number.should == expected
35
+ end
36
+
37
+ it "parses rationals" do
38
+ expected = rand(1_000) + rand
39
+ input = "<number>#{expected}</number>"
40
+ parser.parse(input).number.should == BigDecimal(expected.to_s)
41
+ end
42
+
43
+ it "ignores comments" do
44
+ input = "<time><!-- 65432 --></time>"
45
+ parser.parse(input).number.should be_nil
46
+ end
47
+
48
+ it "ignores children" do
49
+ input = "<time><b>#{rand}</b></time>"
50
+ parser.parse(input).number.should be_nil
51
+ end
52
+
53
+ it "ignores whitespace" do
54
+ expected = rand(1_000)
55
+ input = "<number> #{expected} </number>"
56
+ parser.parse(input).number.should == expected
57
+ end
58
+ end
59
+
60
+ end
@@ -0,0 +1,84 @@
1
+ require "spec_helper"
2
+
3
+ describe XmlParsable::Elements::RecordElement do
4
+
5
+ let(:parser) do
6
+ Class.new do
7
+ include XmlParsable
8
+
9
+ element :date, XmlParsable::Elements::DateElement
10
+ element :string, XmlParsable::Elements::StringElement
11
+ element :text, XmlParsable::Elements::TextElement
12
+ element :time, XmlParsable::Elements::TimeElement
13
+ element :xml, XmlParsable::Elements::XmlElement
14
+ element :number, XmlParsable::Elements::NumericElement
15
+ element :record, self
16
+
17
+ collection :kids, self
18
+ collection :list, :item
19
+ end
20
+ end
21
+
22
+ describe "parser" do
23
+ it "parses multiple elements" do
24
+ time = Time.now
25
+ date = Date.today
26
+
27
+ xml = parser.parse <<-XML
28
+ <record>
29
+ <date>#{date}</date>
30
+ <time>#{time}</time>
31
+ <list>
32
+ <item>a</item>
33
+ <item>b</item>
34
+ <item>c</item>
35
+ </list>
36
+ <string>xyz</string>
37
+ <number>888</number>
38
+ <text>
39
+ <ul>
40
+ <li>vim</li>
41
+ <li>emacs</li>
42
+ <li>ctags</li>
43
+ <li>lint</li>
44
+ <li>gdb</li>
45
+ </ul>
46
+ </text>
47
+ </record>
48
+ XML
49
+
50
+ xml.record.list.should == ["a","b","c"]
51
+ xml.record.string.should == "xyz"
52
+ xml.record.number.should == 888
53
+ xml.record.date.should == date
54
+ xml.record.time.to_i.should == time.to_i
55
+ xml.record.text.strip.should == <<-DOC.strip
56
+ vim
57
+ emacs
58
+ ctags
59
+ lint
60
+ gdb
61
+ DOC
62
+ end
63
+
64
+ it "parses recursive records" do
65
+ xml = parser.parse(<<-XML)
66
+ <record>
67
+ <string>root</string>
68
+ <record>
69
+ <string>child</string>
70
+ <record>
71
+ <string>grand child</string>
72
+ </record>
73
+ </record>
74
+ </record>
75
+ XML
76
+
77
+ xml.string.should be_nil
78
+ xml.record.string.should == "root"
79
+ xml.record.record.string.should == "child"
80
+ xml.record.record.record.string.should == "grand child"
81
+ end
82
+ end
83
+
84
+ end
@@ -0,0 +1,44 @@
1
+ require "spec_helper"
2
+
3
+ describe XmlParsable::Elements::StringElement do
4
+
5
+ describe "parser" do
6
+ let(:parser) do
7
+ Class.new do
8
+ include XmlParsable
9
+ element :string, XmlParsable::Elements::StringElement
10
+ end
11
+ end
12
+
13
+ it "parses empty string" do
14
+ expected = ""
15
+ input = "<string></string>"
16
+ parser.parse(input).string.should == expected
17
+ end
18
+
19
+ it "parses non-empty string" do
20
+ expected = "lo and behold"
21
+ input = "<string>lo and behold</string>"
22
+ parser.parse(input).string.should == expected
23
+ end
24
+
25
+ it "ignores comments" do
26
+ expected = "hi"
27
+ input = "<string>hi<!-- secret message --></string>"
28
+ parser.parse(input).string.should == expected
29
+ end
30
+
31
+ it "ignores children" do
32
+ expected = ", , plain"
33
+ input = "<string><b>bold</b>, <i>italic</i>, plain</string>"
34
+ parser.parse(input).string.should == expected
35
+ end
36
+
37
+ it "does not ignore whitespace" do
38
+ expected = " foob ar"
39
+ input = "<string> foob ar</string>"
40
+ parser.parse(input).string.should == expected
41
+ end
42
+ end
43
+
44
+ end
@@ -0,0 +1,38 @@
1
+ require "spec_helper"
2
+
3
+ describe XmlParsable::Elements::StringElement do
4
+
5
+ describe "parser" do
6
+ let(:parser) do
7
+ Class.new do
8
+ include XmlParsable
9
+ element :text, XmlParsable::Elements::TextElement
10
+ end
11
+ end
12
+
13
+ it "parses text" do
14
+ expected = "lo and behold"
15
+ input = "<text>lo and behold</text>"
16
+ parser.parse(input).text.should == expected
17
+ end
18
+
19
+ it "ignores comments" do
20
+ expected = "lo behold"
21
+ input = "<text>lo <!--and--> behold</text>"
22
+ parser.parse(input).text.should == expected
23
+ end
24
+
25
+ it "strips away markup" do
26
+ expected = "lo and behold"
27
+ input = "<text>lo <span class='x'><i>and</i> behold</span></text>"
28
+ parser.parse(input).text.should == expected
29
+ end
30
+
31
+ it "does not ignore whitespace" do
32
+ expected = " foob ar"
33
+ input = "<text> foob ar</text>"
34
+ parser.parse(input).text.should == expected
35
+ end
36
+ end
37
+
38
+ end
@@ -0,0 +1,36 @@
1
+ require "spec_helper"
2
+
3
+ describe XmlParsable::Elements::StringElement do
4
+
5
+ describe "parser" do
6
+ let(:parser) do
7
+ Class.new do
8
+ include XmlParsable
9
+ element :time, XmlParsable::Elements::TimeElement
10
+ end
11
+ end
12
+
13
+ it "parses time" do
14
+ expected = Time.now
15
+ input = "<time>#{expected}</time>"
16
+ parser.parse(input).time.to_i.should == expected.to_i
17
+ end
18
+
19
+ it "ignores comments" do
20
+ input = "<time><!-- #{Time.now} --></time>"
21
+ parser.parse(input).time.should be_nil
22
+ end
23
+
24
+ it "ignores children" do
25
+ input = "<time><b>#{Time.now}</b></time>"
26
+ parser.parse(input).time.should be_nil
27
+ end
28
+
29
+ it "ignores whitespace" do
30
+ expected = Time.now
31
+ input = "<time> #{expected} </time>"
32
+ parser.parse(input).time.to_i.should == expected.to_i
33
+ end
34
+ end
35
+
36
+ end
@@ -0,0 +1 @@
1
+ require "spec_helper"
@@ -1,154 +1,130 @@
1
1
  require "spec_helper"
2
2
 
3
- describe XmlParsable::Elements::RecordElement, "inheritance" do
3
+ describe XmlParsable, "inheritance" do
4
+
5
+ let(:input) do
6
+ <<-XML
7
+ <root>
8
+ <!-- This is parsed as a Date, Integer, or String -->
9
+ <id>20120531</id>
10
+ <inherited>11235</inherited>
11
+
12
+ <a>some a-data</a>
13
+ <b>some b-data</b>
14
+ <c>
15
+ <id>IDENTIFIER</id>
16
+ <inherited></inherited>
17
+ </c>
18
+ </root>
19
+ XML
20
+ end
4
21
 
5
- class BaseRecord
6
- include XmlParsable
22
+ let(:common) do
23
+ Class.new do
24
+ attr_reader :xmlelems
7
25
 
8
- class Record
9
26
  include XmlParsable
10
- element :id, StringElement
11
- element :size, NumericElement
27
+ element :id
28
+ element :inherited, XmlParsable::Elements::NumericElement
12
29
  end
13
-
14
- element :record, Record
15
30
  end
16
31
 
17
- class FirstRecord
18
- include XmlParsable
32
+ let(:base) do
33
+ root = common
34
+ Class.new do
35
+ include XmlParsable
36
+ element :root, root
37
+ end
38
+ end
19
39
 
20
- class Record < BaseRecord::Record
21
- element :id, TimeElement
22
- element :name, StringElement
40
+ # Override the parent's id element
41
+ let(:a) do
42
+ parent = common
43
+ Class.new do
44
+ inner = Class.new(parent) do
45
+ element :id, XmlParsable::Elements::TimeElement
46
+ element :a
47
+ end
48
+ include XmlParsable
49
+ element :root, inner
23
50
  end
51
+ end
24
52
 
25
- element :record, Record
53
+ # Override the parent's id element
54
+ let(:b) do
55
+ parent = common
56
+ Class.new do
57
+ inner = Class.new(parent) do
58
+ element :id, XmlParsable::Elements::NumericElement
59
+ element :b
60
+ end
61
+ include XmlParsable
62
+ element :root, inner
63
+ end
26
64
  end
27
65
 
28
- class SecondRecord
29
- include XmlParsable
66
+ let(:c) do
67
+ parent = common
30
68
 
31
- class Record < BaseRecord::Record
32
- element :id, NumericElement
33
- end
69
+ Class.new do
70
+ inner = Class.new(parent) do
71
+ element :c, self
72
+ end
34
73
 
35
- element :record, Record
74
+ include XmlParsable
75
+ element :root, inner
76
+ end
36
77
  end
37
78
 
38
- class ThirdRecord < BaseRecord
39
- include XmlParsable
79
+ describe "base parser" do
80
+ it "is separate from derived parsers" do
81
+ xml = base.parse(input)
82
+ xml.root.id.should == "20120531"
83
+ xml.root.inherited.should == 11235
40
84
 
41
- class Record < BaseRecord::Record
42
- element :parent, Record
43
- end
85
+ xml.root.xmlelems.should_not have_key(:a)
86
+ xml.root.xmlelems.should_not have_key(:b)
87
+ xml.root.xmlelems.should_not have_key(:c)
44
88
 
45
- element :record, Record
89
+ xml.root.should_not respond_to(:a)
90
+ xml.root.should_not respond_to(:b)
91
+ xml.root.should_not respond_to(:c)
92
+ end
46
93
  end
47
94
 
48
- describe "parent" do
49
- subject { BaseRecord::Record.new }
50
- specify { should respond_to(:id) }
51
- specify { should respond_to(:size) }
52
- specify { should_not respond_to(:name) }
53
- specify { should_not respond_to(:parent) }
54
-
55
- it "parses attributes" do
56
- data = BaseRecord.parse(<<-XML.strip)
57
- <record>
58
- <id>0xCAFE</id>
59
- <size>0xFE</size>
60
- </record>
61
- XML
62
-
63
- data.record.should be_a(BaseRecord::Record)
64
- data.record.id.should == "0xCAFE"
65
- data.record.size.should == 0xFE
95
+ describe "derived parsers" do
96
+ it "inherit elements from base parser" do
97
+ a.parse(input).root.inherited.should == 11235
98
+ b.parse(input).root.inherited.should == 11235
99
+ c.parse(input).root.inherited.should == 11235
66
100
  end
67
- end
68
101
 
69
- describe "first" do
70
- subject { FirstRecord::Record.new }
71
- specify { should respond_to(:id) }
72
- specify { should respond_to(:size) }
73
- specify { should respond_to(:name) }
74
- specify { should_not respond_to(:parent) }
75
-
76
- it "parses attributes" do
77
- data = FirstRecord.parse(<<-XML.strip)
78
- <record>
79
- <id>20120530T154500</id>
80
- <size>0xCA7</size>
81
- <name>Sabji</name>
82
- </record>
83
- XML
84
-
85
- data.record.should be_a(FirstRecord::Record)
86
- data.record.id.should == Time.local(2012, 5, 30, 15, 45)
87
- data.record.size.should == 0xCA7
88
- data.record.name.should == "Sabji"
102
+ it "can specify additional elements" do
103
+ a.parse(input).root.a.should == "some a-data"
104
+ b.parse(input).root.b.should == "some b-data"
105
+
106
+ c.parse(input).root.c.should_not be_nil
107
+ c.parse(input).root.c.id.should == "IDENTIFIER"
108
+ c.parse(input).root.c.inherited.should be_nil
89
109
  end
90
- end
91
110
 
92
- describe "second" do
93
- subject { SecondRecord::Record.new }
94
- specify { should respond_to(:id) }
95
- specify { should respond_to(:size) }
96
- specify { should_not respond_to(:name) }
97
- specify { should_not respond_to(:parent) }
98
-
99
- it "parses attributes" do
100
- data = SecondRecord.parse(<<-XML)
101
- <record>
102
- <id>0.314</id>
103
- <size>3.014</size>
104
- </record>
105
- XML
106
-
107
- data.record.should be_a(SecondRecord::Record)
108
- data.record.id.should == 0.314
109
- data.record.size.should == 3.014
111
+ it "can override an inherited element" do
112
+ a.parse(input).root.id.should be_a(Time)
113
+ b.parse(input).root.id.should be_a(Integer)
110
114
  end
111
- end
112
115
 
113
- describe "third" do
114
- subject { ThirdRecord::Record.new }
115
- specify { should respond_to(:id) }
116
- specify { should respond_to(:size) }
117
- specify { should_not respond_to(:name) }
118
- specify { should respond_to(:parent) }
119
-
120
- it "parses attributes" do
121
- data = ThirdRecord.parse(<<-XML)
122
- <record>
123
- <id>X039-P99-THX1138-U</id>
124
- <size>11235813</size>
125
- <parent>
126
- <id>X039-P99-THX1138</id>
127
- <size>112358</size>
128
- <parent>
129
- <id>X039-P99</id>
130
- <size>11235</size>
131
- <parent>
132
- <id>X039</id>
133
- <size>1123</size>
134
- </parent>
135
- </parent>
136
- </parent>
137
- </record>
138
- XML
139
-
140
- data.record.should be_a(ThirdRecord::Record)
141
- data.record.id.should == "X039-P99-THX1138-U"
142
- data.record.size.should == 11235813
143
-
144
- data.record.parent.id.should == "X039-P99-THX1138"
145
- data.record.parent.size.should == 112358
146
-
147
- data.record.parent.parent.id.should == "X039-P99"
148
- data.record.parent.parent.size.should == 11235
149
-
150
- data.record.parent.parent.parent.id.should == "X039"
151
- data.record.parent.parent.parent.size.should == 1123
116
+ specify "are independent from one another" do
117
+ xml = a.parse(input).root
118
+ xml.should_not respond_to(:b)
119
+ xml.should_not respond_to(:c)
120
+
121
+ xml = b.parse(input).root
122
+ xml.should_not respond_to(:a)
123
+ xml.should_not respond_to(:c)
124
+
125
+ xml = c.parse(input).root
126
+ xml.should_not respond_to(:a)
127
+ xml.should_not respond_to(:b)
152
128
  end
153
129
  end
154
130
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xmlparsable
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.2
4
+ version: 1.5.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -36,8 +36,8 @@ files:
36
36
  - README.md
37
37
  - Rakefile
38
38
  - lib/xmlparsable/elements/abstract.rb
39
+ - lib/xmlparsable/elements/array.rb
39
40
  - lib/xmlparsable/elements/date.rb
40
- - lib/xmlparsable/elements/list.rb
41
41
  - lib/xmlparsable/elements/numeric.rb
42
42
  - lib/xmlparsable/elements/record.rb
43
43
  - lib/xmlparsable/elements/string.rb
@@ -47,8 +47,16 @@ files:
47
47
  - lib/xmlparsable/elements.rb
48
48
  - lib/xmlparsable/parser.rb
49
49
  - lib/xmlparsable.rb
50
+ - spec/examples/accessors.example
51
+ - spec/examples/elements/array.example
52
+ - spec/examples/elements/date.example
53
+ - spec/examples/elements/numeric.example
54
+ - spec/examples/elements/record.example
55
+ - spec/examples/elements/string.example
56
+ - spec/examples/elements/text.example
57
+ - spec/examples/elements/time.example
58
+ - spec/examples/elements/xml.example
50
59
  - spec/examples/inheritance.example
51
- - spec/examples/record.example
52
60
  - spec/spec_helper.rb
53
61
  homepage: https://github.com/kputnam/xmlparsable
54
62
  licenses: []
@@ -75,5 +83,13 @@ signing_key:
75
83
  specification_version: 3
76
84
  summary: Simple declarative XML parsing
77
85
  test_files:
86
+ - spec/examples/accessors.example
87
+ - spec/examples/elements/array.example
88
+ - spec/examples/elements/date.example
89
+ - spec/examples/elements/numeric.example
90
+ - spec/examples/elements/record.example
91
+ - spec/examples/elements/string.example
92
+ - spec/examples/elements/text.example
93
+ - spec/examples/elements/time.example
94
+ - spec/examples/elements/xml.example
78
95
  - spec/examples/inheritance.example
79
- - spec/examples/record.example
@@ -1,31 +0,0 @@
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 close(element, value)
22
- @elements << value
23
- end
24
-
25
- def finalize
26
- @parent.close(self, @elements)
27
- end
28
- end
29
-
30
- end
31
- end
@@ -1,260 +0,0 @@
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 :string, StringElement
10
- element :text, TextElement
11
- element :time, TimeElement
12
- element :xml, XmlElement
13
- element :number, NumericElement
14
- element :record, self
15
-
16
- collection :kids, self
17
- collection :list, :item
18
- end
19
-
20
- describe "date" do
21
- it "parses date into a ruby Date" do
22
- Record.parse("<date>#{Date.today}</date>").date.should == Date.today
23
- end
24
-
25
- it "parses missing date as nil" do
26
- Record.parse("<date><!-- nope --></date>").date.should == nil
27
- end
28
-
29
- it "ignores children" do
30
- Record.parse("<date><x>2010-01-01</x></date>").date.should == nil
31
- end
32
-
33
- it "ignores comments" do
34
- Record.parse("<date>#{Date.today} <!-- that's today --></date>").date.should == Date.today
35
- end
36
-
37
- it "ignores whitespace" do
38
- Record.parse("<date>\n #{Date.today}\n </date>").date.should == Date.today
39
- end
40
- end
41
-
42
- describe "list of strings" do
43
- it "parses empty list into an empty Array" do
44
- Record.parse("<list/>").list.should == []
45
- end
46
-
47
- it "parses non-empty list into an Array" do
48
- Record.parse("<list><item>a</item><item>b</item></list>").list.should == ["a", "b"]
49
- end
50
-
51
- it "ignores non-matching children" do
52
- Record.parse("<list><fail/><item/><boat/><item/><item/></list>").list.should == ["", "", ""]
53
- end
54
-
55
- it "ignores comments" do
56
- Record.parse("<list><!-- <item>x</item> --><item><!-- y --></item></list>").list.should == [""]
57
- end
58
-
59
- it "ignores whitespace between children" do
60
- Record.parse(<<-XML).list.should == ["a", "b", "c"]
61
- <list>
62
- <item>a</item>
63
- <item>b</item>
64
- <item>c</item>
65
- </list>
66
- XML
67
- end
68
- end
69
-
70
- describe "list of records" do
71
- it "parses empty list into an empty Array" do
72
- Record.parse("<kids/>").kids.should == []
73
- end
74
-
75
- it "parses non-empty list into an array" do
76
- x = Record.parse(<<-XML)
77
- <kids>
78
- <kid><text>first</text></kid>
79
- <kid><text>second</text></kid>
80
- <kid><text>third</text></kid>
81
- </kids>
82
- XML
83
-
84
- x.kids.should have(3).items
85
- x.kids[0].text.should == "first"
86
- x.kids[1].text.should == "second"
87
- x.kids[2].text.should == "third"
88
- end
89
- end
90
-
91
- describe "string" do
92
- it "parses empty string" do
93
- Record.parse("<string></string>").string.should == ""
94
- end
95
-
96
- it "parses non-empty string" do
97
- Record.parse("<string>lo and behold</string>").string.should == "lo and behold"
98
- end
99
-
100
- it "ignores comments" do
101
- Record.parse("<string>hi<!-- secret message --></string>").string.should == "hi"
102
- end
103
-
104
- it "ignores children" do
105
- Record.parse("<string><b>bold</b>, <i>italic</i>, plain</string>").string.should == ", , plain"
106
- end
107
-
108
- it "does not ignore whitespace" do
109
- Record.parse("<string> foob ar</string>").string.should == " foob ar"
110
- end
111
- end
112
-
113
- describe "text" do
114
- it "parses text" do
115
- Record.parse("<text>lo and behold</text>").text.should == "lo and behold"
116
- end
117
-
118
- it "ignores comments" do
119
- Record.parse("<text>lo <!--and--> behold</text>").text.should == "lo behold"
120
- end
121
-
122
- it "strips away markup" do
123
- Record.parse("<text>lo <span class='x'><i>and</i> behold</span></text>").text.should == "lo and behold"
124
- end
125
-
126
- it "does not ignore whitespace" do
127
- Record.parse("<text> foob ar</text>").text.should == " foob ar"
128
- end
129
- end
130
-
131
- describe "time" do
132
- it "parses time" do
133
- time = Time.now
134
- Record.parse("<time>#{time}</time>").time.to_i.should == time.to_i
135
- end
136
-
137
- it "ignores comments" do
138
- Record.parse("<time><!-- #{Time.now} --></time>").time.should be_nil
139
- end
140
-
141
- it "ignores children" do
142
- Record.parse("<time><b>#{Time.now}</b></time>").time.should be_nil
143
- end
144
-
145
- it "ignores whitespace" do
146
- time = Time.now
147
- Record.parse("<time> #{time} </time>").time.to_i.should == time.to_i
148
- end
149
- end
150
-
151
- describe "number" do
152
- it "parses integers" do
153
- number = rand(1_000)
154
- Record.parse("<number>#{number}</number>").number.should == number
155
- end
156
-
157
- it "parses rationals" do
158
- number = rand(1_000) + rand
159
- Record.parse("<number>#{number}</number>").number.should == BigDecimal(number.to_s)
160
- end
161
-
162
- it "ignores comments" do
163
- Record.parse("<time><!-- 65432 --></time>").number.should be_nil
164
- end
165
-
166
- it "ignores children" do
167
- Record.parse("<time><b>#{rand}</b></time>").number.should be_nil
168
- end
169
-
170
- it "ignores whitespace" do
171
- number = rand(1_000)
172
- Record.parse("<number> #{number} </number>").number.should == number
173
- end
174
- end
175
-
176
- describe "record" do
177
- it "parses date" do
178
- x = Record.parse("<record><date>#{Date.today}</date></record>")
179
- x.date.should be_nil
180
- x.record.date.should == Date.today
181
- end
182
-
183
- it "parses list" do
184
- x = Record.parse("<record><list/></record>")
185
- x.list.should be_nil
186
- x.record.list.should == []
187
- end
188
-
189
- it "parses string" do
190
- x = Record.parse("<record><string>abc</string></record>")
191
- x.string.should be_nil
192
- x.record.string.should == "abc"
193
- end
194
-
195
- it "parses text" do
196
- x = Record.parse("<record><text><b>big</b><i>little</i></text></record>")
197
- x.text.should be_nil
198
- x.record.text.should == "biglittle"
199
- end
200
-
201
- it "parses time" do
202
- time = Time.now
203
- Record.parse("<record><time>#{time}</time></record>").record.time.to_i.should == time.to_i
204
- end
205
-
206
- it "parses number" do
207
- number = rand(1_000) + rand
208
- Record.parse("<record><number>#{number}</number></record>").record.number.should == BigDecimal(number.to_s)
209
- end
210
-
211
- it "parses multiple elements" do
212
- time = Time.now
213
- x = Record.parse <<-XML
214
- <record>
215
- <date>#{Date.today}</date>
216
- <list>
217
- <item>a</item>
218
- <item>b</item>
219
- <item>c</item>
220
- </list>
221
- <string>xyz</string>
222
- <text>
223
- <ul>
224
- <li>vim</li>
225
- <li>emacs</li>
226
- <li>ctags</li>
227
- <li>lint</li>
228
- <li>gdb</li>
229
- </ul>
230
- </text>
231
- <time>#{time}</time>
232
- </record>
233
- XML
234
-
235
- x.record.date.should == Date.today
236
- x.record.list.should == ["a","b","c"]
237
- x.record.string.should == "xyz"
238
- x.record.text.strip.should == <<-DOC.strip
239
- vim
240
- emacs
241
- ctags
242
- lint
243
- gdb
244
- DOC
245
-
246
- x.record.time.to_i.should == time.to_i
247
- end
248
-
249
- it "parses recursive records" do
250
- x = Record.parse("<record><record><record><record></record></record></record></record>")
251
- x.should be_a(Record)
252
- x.record.should be_a(Record)
253
- x.record.record.should be_a(Record)
254
- x.record.record.record.should be_a(Record)
255
- x.record.record.record.record.should be_a(Record)
256
- x.record.record.record.record.record.should be_nil
257
- end
258
- end
259
-
260
- end