xmlparsable 1.4.2 → 1.5.0

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.
@@ -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