julien51-sax-machine 0.0.16 → 0.0.22

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.
@@ -3,21 +3,44 @@ module SAXMachine
3
3
 
4
4
  class CollectionConfig
5
5
  attr_reader :name
6
+ attr_reader :default_xmlns
6
7
 
7
8
  def initialize(name, options)
8
9
  @name = name.to_s
9
10
  @class = options[:class]
10
11
  @as = options[:as].to_s
12
+ @xmlns = case options[:xmlns]
13
+ when Array then options[:xmlns]
14
+ when String then [options[:xmlns]]
15
+ else nil
16
+ end
17
+ @default_xmlns = options[:default_xmlns]
18
+ if @default_xmlns && @xmlns && !@xmlns.include?('')
19
+ @xmlns << ''
20
+ end
21
+ @record_events = options[:events]
11
22
  end
12
23
 
13
- def handler
14
- SAXHandler.new(@class.new)
24
+ def handler(nsstack)
25
+ if @default_xmlns && (nsstack.nil? || nsstack[''] == '')
26
+ nsstack = NSStack.new(nsstack, nsstack)
27
+ nsstack[''] = @default_xmlns
28
+ end
29
+ unless @record_events
30
+ SAXHandler.new(@class.new, nsstack)
31
+ else
32
+ SAXEventRecorder.new(nsstack)
33
+ end
15
34
  end
16
35
 
17
36
  def accessor
18
37
  as
19
38
  end
20
39
 
40
+ def xmlns_match?(ns)
41
+ @xmlns.nil? || @xmlns.include?(ns)
42
+ end
43
+
21
44
  protected
22
45
 
23
46
  def as
@@ -30,4 +53,4 @@ module SAXMachine
30
53
  end
31
54
 
32
55
  end
33
- end
56
+ end
@@ -6,25 +6,34 @@ module SAXMachine
6
6
  attr_reader :top_level_elements, :collection_elements
7
7
 
8
8
  def initialize
9
- @top_level_elements = []
10
- @collection_elements = []
9
+ @top_level_elements = {}
10
+ @collection_elements = {}
11
11
  end
12
12
 
13
13
  def add_top_level_element(name, options)
14
- @top_level_elements << ElementConfig.new(name, options)
14
+ @top_level_elements[name.to_s] ||= []
15
+ @top_level_elements[name.to_s] << ElementConfig.new(name, options)
15
16
  end
16
17
 
17
18
  def add_collection_element(name, options)
18
- @collection_elements << CollectionConfig.new(name, options)
19
+ @collection_elements[name.to_s] ||= []
20
+ @collection_elements[name.to_s] << CollectionConfig.new(name, options)
19
21
  end
20
22
 
21
- def collection_config(name)
22
- @collection_elements.detect { |ce| ce.name.to_s == name.to_s }
23
+ def collection_config(name, nsstack)
24
+ prefix, name = name.split(':', 2)
25
+ prefix, name = nil, prefix unless name # No prefix
26
+ namespace = nsstack[prefix]
27
+
28
+ (@collection_elements[name.to_s] || []).detect { |ce|
29
+ ce.name.to_s == name.to_s &&
30
+ ce.xmlns_match?(namespace)
31
+ }
23
32
  end
24
33
 
25
34
  def element_configs_for_attribute(name, attrs)
26
- @top_level_elements.select do |element_config|
27
- element_config.name == name &&
35
+ name = name.split(':', 2).last
36
+ (@top_level_elements[name.to_s] || []).select do |element_config|
28
37
  element_config.has_value_and_attrs_match?(attrs)
29
38
  end
30
39
  end
@@ -34,8 +43,7 @@ module SAXMachine
34
43
  prefix, name = nil, prefix unless name # No prefix
35
44
  namespace = nsstack[prefix]
36
45
 
37
- @top_level_elements.detect do |element_config|
38
- element_config.name == name &&
46
+ (@top_level_elements[name.to_s] || []).detect do |element_config|
39
47
  element_config.xmlns_match?(namespace) &&
40
48
  element_config.attrs_match?(attrs)
41
49
  end
@@ -10,6 +10,7 @@ module SAXMachine
10
10
  unless @parser
11
11
  sax_handler = SAXHandler.new(self)
12
12
  @parser = Nokogiri::XML::SAX::PushParser.new(sax_handler)
13
+ @parser.options |= Nokogiri::XML::ParseOptions::RECOVER if @parser.respond_to?(:options)
13
14
  end
14
15
  @parser << xml_text
15
16
  self
@@ -43,11 +44,15 @@ module SAXMachine
43
44
  end
44
45
 
45
46
  def columns
46
- sax_config.top_level_elements
47
+ r = []
48
+ sax_config.top_level_elements.each do |name, ecs|
49
+ r += ecs
50
+ end
51
+ r
47
52
  end
48
53
 
49
54
  def column(sym)
50
- columns.select{|c| c.column == sym}[0]
55
+ (sax_config.top_level_elements[sym.to_s] || []).first
51
56
  end
52
57
 
53
58
  def data_class(sym)
@@ -64,7 +69,7 @@ module SAXMachine
64
69
 
65
70
  def elements(name, options = {})
66
71
  options[:as] ||= name
67
- if options[:class]
72
+ if options[:class] || options[:events]
68
73
  sax_config.add_collection_element(name, options)
69
74
  else
70
75
  class_eval <<-SRC
@@ -0,0 +1,35 @@
1
+ module SAXMachine
2
+ class SAXEventRecorder < SAXHandler
3
+ def initialize(nsstack)
4
+ super(nil, nsstack)
5
+ @events = []
6
+ end
7
+
8
+ def object
9
+ # First and last belong to the parent element
10
+ @events[1..-2]
11
+ end
12
+
13
+ def start_element(name, attrs = [])
14
+ @nsstack = NSStack.new(@nsstack, attrs)
15
+ prefix, name = name.split(':', 2)
16
+ prefix, name = nil, prefix unless name
17
+ @events << [:start_element, @nsstack[prefix], name, attrs]
18
+ end
19
+
20
+ def end_element(name)
21
+ prefix, name = name.split(':', 2)
22
+ prefix, name = nil, prefix unless name
23
+ @events << [:end_element, @nsstack[prefix], name]
24
+ @nsstack = @nsstack.pop
25
+ end
26
+
27
+ def characters(string)
28
+ @events << [:chars, string]
29
+ end
30
+
31
+ def sax_config
32
+ raise
33
+ end
34
+ end
35
+ end
@@ -5,9 +5,9 @@ module SAXMachine
5
5
  class SAXHandler < Nokogiri::XML::SAX::Document
6
6
  attr_reader :object
7
7
 
8
- def initialize(object)
8
+ def initialize(object, nsstack=nil)
9
9
  @object = object
10
- @nsstack = nil
10
+ @nsstack = nsstack || NSStack.new
11
11
  end
12
12
 
13
13
  def characters(string)
@@ -23,7 +23,7 @@ module SAXMachine
23
23
  end
24
24
 
25
25
  def start_element(name, attrs = [])
26
-
26
+
27
27
  @name = name
28
28
  @attrs = attrs.map { |a| SAXHandler.decode_xml(a) }
29
29
  @nsstack = NSStack.new(@nsstack, @attrs)
@@ -31,10 +31,11 @@ module SAXMachine
31
31
  if parsing_collection?
32
32
  @collection_handler.start_element(@name, @attrs)
33
33
 
34
- elsif @collection_config = sax_config.collection_config(@name)
35
- @collection_handler = @collection_config.handler
36
- @collection_handler.start_element(@name, @attrs)
37
-
34
+ elsif @collection_config = sax_config.collection_config(@name, @nsstack)
35
+ @collection_handler = @collection_config.handler(@nsstack)
36
+ if @object.class != @collection_handler.object.class
37
+ @collection_handler.start_element(@name, @attrs)
38
+ end
38
39
  elsif (element_configs = sax_config.element_configs_for_attribute(@name, @attrs)).any?
39
40
  parse_element_attributes(element_configs)
40
41
  set_element_config_for_element_value
@@ -45,7 +46,8 @@ module SAXMachine
45
46
  end
46
47
 
47
48
  def end_element(name)
48
- if parsing_collection? && @collection_config.name == name
49
+ if parsing_collection? && @collection_config.name == name.split(':').last
50
+ @collection_handler.end_element(name)
49
51
  @object.send(@collection_config.accessor) << @collection_handler.object
50
52
  reset_current_collection
51
53
 
data/lib/sax-machine.rb CHANGED
@@ -5,7 +5,8 @@ require "cgi"
5
5
  require "sax-machine/sax_document"
6
6
  require "sax-machine/sax_handler"
7
7
  require "sax-machine/sax_config"
8
+ require "sax-machine/sax_event_recorder"
8
9
 
9
10
  module SAXMachine
10
- VERSION = "0.0.16"
11
- end
11
+ VERSION = "0.0.21"
12
+ end
@@ -326,6 +326,46 @@ describe "SAXMachine" do
326
326
  document = @klass.parse("<x:b>hello</x:b>")
327
327
  document.b.should == 'hello'
328
328
  end
329
+
330
+ it "should get the namespaced element even it's not first" do
331
+ document = @klass.parse("<root xmlns:a='urn:test'><a>foo</a><a>foo</a><a:a>bar</a:a></root>")
332
+ document.a.should == 'bar'
333
+ end
334
+
335
+ it "should parse multiple namespaces" do
336
+ klass = Class.new do
337
+ include SAXMachine
338
+ element :a, :xmlns => 'urn:test'
339
+ element :b, :xmlns => 'urn:test2'
340
+ end
341
+ document = klass.parse("<root xmlns='urn:test' xmlns:b='urn:test2'><b:b>bar</b:b><a>foo</a></root>")
342
+ document.a.should == 'foo'
343
+ document.b.should == 'bar'
344
+ end
345
+
346
+ context "when passing a default namespace" do
347
+ before :all do
348
+ @xmlns = 'urn:test'
349
+ class Inner
350
+ include SAXMachine
351
+ element :a, :xmlns => @xmlns
352
+ end
353
+ @outer = Class.new do
354
+ include SAXMachine
355
+ elements :root, :default_xmlns => @xmlns, :class => Inner
356
+ end
357
+ end
358
+
359
+ it "should replace the empty namespace with a default" do
360
+ document = @outer.parse("<root><a>Hello</a></root>")
361
+ document.root[0].a.should == 'Hello'
362
+ end
363
+
364
+ it "should not replace another namespace" do
365
+ document = @outer.parse("<root xmlns='urn:test2'><a>Hello</a></root>")
366
+ document.root[0].a.should == 'Hello'
367
+ end
368
+ end
329
369
  end
330
370
 
331
371
  end
@@ -333,7 +373,7 @@ describe "SAXMachine" do
333
373
 
334
374
  describe "elements" do
335
375
  describe "when parsing multiple elements" do
336
- before :each do
376
+ before :all do
337
377
  @klass = Class.new do
338
378
  include SAXMachine
339
379
  elements :entry, :as => :entries
@@ -407,16 +447,63 @@ describe "SAXMachine" do
407
447
  document.entries.first.url.should == "http://pauldix.net"
408
448
  end
409
449
  end
450
+
451
+ describe "when desiring sax events" do
452
+ XHTML_XMLNS = "http://www.w3.org/1999/xhtml"
453
+
454
+ before :all do
455
+ @klass = Class.new do
456
+ include SAXMachine
457
+ elements :body, :events => true
458
+ end
459
+ end
460
+
461
+ it "should parse a simple child" do
462
+ document = @klass.parse("<body><p/></body>")
463
+ document.body[0].should == [[:start_element, "", "p", []],
464
+ [:end_element, "", "p"]]
465
+ end
466
+ it "should parse a simple child with text" do
467
+ document = @klass.parse("<body><p>Hello</p></body>")
468
+ document.body[0].should == [[:start_element, "", "p", []],
469
+ [:chars, "Hello"],
470
+ [:end_element, "", "p"]]
471
+ end
472
+ it "should parse nested children" do
473
+ document = @klass.parse("<body><p><span/></p></body>")
474
+ document.body[0].should == [[:start_element, "", "p", []],
475
+ [:start_element, "", "span", []],
476
+ [:end_element, "", "span"],
477
+ [:end_element, "", "p"]]
478
+ end
479
+ it "should parse multiple children" do
480
+ document = @klass.parse("<body><p>Hello</p><p>World</p></body>")
481
+ document.body[0].should == [[:start_element, "", "p", []],
482
+ [:chars, "Hello"],
483
+ [:end_element, "", "p"],
484
+ [:start_element, "", "p", []],
485
+ [:chars, "World"],
486
+ [:end_element, "", "p"]]
487
+ end
488
+ it "should pass namespaces" do
489
+ document = @klass.parse("<body xmlns='#{XHTML_XMLNS}'><p/></body>")
490
+ document.body[0].should == [[:start_element, XHTML_XMLNS, "p", []],
491
+ [:end_element, XHTML_XMLNS, "p"]]
492
+ end
493
+ end
410
494
  end
411
495
 
412
496
  describe "full example" do
497
+ XMLNS_ATOM = "http://www.w3.org/2005/Atom"
498
+ XMLNS_FEEDBURNER = "http://rssnamespace.org/feedburner/ext/1.0"
499
+
413
500
  before :each do
414
501
  @xml = File.read('spec/sax-machine/atom.xml')
415
502
  class AtomEntry
416
503
  include SAXMachine
417
504
  element :title
418
505
  element :name, :as => :author
419
- element "feedburner:origLink", :as => :url
506
+ element :origLink, :as => :orig_link, :xmlns => XMLNS_FEEDBURNER
420
507
  element :summary
421
508
  element :content
422
509
  element :published
@@ -427,7 +514,7 @@ describe "SAXMachine" do
427
514
  element :title
428
515
  element :link, :value => :href, :as => :url, :with => {:type => "text/html"}
429
516
  element :link, :value => :href, :as => :feed_url, :with => {:type => "application/atom+xml"}
430
- elements :entry, :as => :entries, :class => AtomEntry
517
+ elements :entry, :as => :entries, :class => AtomEntry, :xmlns => XMLNS_ATOM
431
518
  end
432
519
  end # before
433
520
 
@@ -435,5 +522,136 @@ describe "SAXMachine" do
435
522
  f = Atom.parse(@xml)
436
523
  f.url.should == "http://www.pauldix.net/"
437
524
  end
525
+
526
+ it "should parse all entries" do
527
+ f = Atom.parse(@xml)
528
+ f.entries.length.should == 5
529
+ end
530
+
531
+ it "should parse the feedburner:origLink" do
532
+ f = Atom.parse(@xml)
533
+ f.entries[0].orig_link.should == 'http://www.pauldix.net/2008/09/marshal-data-to.html'
534
+ end
535
+ end
536
+
537
+ describe "another full example" do
538
+
539
+ RSS_XMLNS = 'http://purl.org/rss/1.0/'
540
+ ATOM_XMLNS = 'http://www.w3.org/2005/Atom'
541
+ class Entry
542
+ include SAXMachine
543
+ element :title, :xmlns => RSS_XMLNS
544
+ element :title, :xmlns => ATOM_XMLNS
545
+ element :link, :xmlns => RSS_XMLNS
546
+ element :link, :xmlns => ATOM_XMLNS, :value => 'href'
547
+ end
548
+ class Channel
549
+ include SAXMachine
550
+ element :title, :xmlns => RSS_XMLNS
551
+ element :title, :xmlns => ATOM_XMLNS
552
+ element :link, :xmlns => RSS_XMLNS
553
+ element :link, :xmlns => ATOM_XMLNS, :value => 'href'
554
+ elements :entry, :as => :entries, :class => Entry
555
+ elements :item, :as => :entries, :class => Entry
556
+ end
557
+ class Root
558
+ include SAXMachine
559
+ elements :rss, :as => :channels, :default_xmlns => RSS_XMLNS, :class => Channel
560
+ elements :feed, :as => :channels, :default_xmlns => ATOM_XMLNS, :class => Channel
561
+ end
562
+
563
+ context "when parsing a complex example" do
564
+ before :all do
565
+ @document = Root.parse(<<-eoxml).channels[0]
566
+ <?xml version="1.0" encoding="UTF-8"?>
567
+ <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"
568
+ xmlns:content="http://purl.org/rss/1.0/modules/content/"
569
+ xmlns:wfw="http://wellformedweb.org/CommentAPI/"
570
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
571
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
572
+ xmlns:cc="http://web.resource.org/cc/">
573
+ <channel>
574
+ <title>Delicious/tag/pubsubhubbub</title>
575
+ <atom:link rel="self" type="application/rss+xml" href="http://feeds.delicious.com/v2/rss/tag/pubsubhubbub?count=15"/>
576
+ <link>http://delicious.com/tag/pubsubhubbub</link>
577
+ <description>recent bookmarks tagged pubsubhubbub</description>
578
+ </channel>
579
+ </rss>
580
+ eoxml
581
+ end
582
+ it "should parse the title" do
583
+ @document.title.should == 'Delicious/tag/pubsubhubbub'
584
+ end
585
+ it "should parse the link" do
586
+ @document.link.should == 'http://feeds.delicious.com/v2/rss/tag/pubsubhubbub?count=15'
587
+ end
588
+ end
589
+ end
590
+
591
+ describe "yet another full example" do
592
+
593
+ context "when parsing a Twitter example" do
594
+ before :all do
595
+
596
+ RSS_XMLNS = ['http://purl.org/rss/1.0/', '']
597
+
598
+ ATOM_XMLNS = 'http://www.w3.org/2005/Atom' unless defined? ATOM_XMLNS
599
+ class Link
600
+ include SAXMachine
601
+ end
602
+
603
+ class Entry
604
+ include SAXMachine
605
+ element :title, :xmlns => RSS_XMLNS
606
+ element :link, :xmlns => RSS_XMLNS, :as => :entry_link
607
+ element :title, :xmlns => ATOM_XMLNS, :as => :title
608
+ elements :link, :xmlns => ATOM_XMLNS, :as => :links, :class => Link
609
+ end
610
+
611
+ class Feed
612
+ include SAXMachine
613
+ element :title, :xmlns => RSS_XMLNS, :as => :title
614
+ element :link, :xmlns => RSS_XMLNS, :as => :feed_link
615
+ elements :item, :xmlns => RSS_XMLNS, :as => :entries, :class => Entry
616
+ element :title, :xmlns => ATOM_XMLNS, :as => :title
617
+ elements :link, :xmlns => ATOM_XMLNS, :as => :links, :class => Link
618
+ end
619
+
620
+ @document = Feed.parse(<<-eoxml)
621
+ <?xml version="1.0" encoding="UTF-8"?>
622
+ <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
623
+ <channel>
624
+ <atom:link type="application/rss+xml" rel="self" href="http://twitter.com/statuses/user_timeline/5381582.rss"/>
625
+ <title>Twitter / julien51</title>
626
+ <link>http://twitter.com/julien51</link>
627
+ <description>Twitter updates from julien / julien51.</description>
628
+ <language>en-us</language>
629
+ <ttl>40</ttl>
630
+ <item>
631
+ <title>julien51: @github : I get an error when trying to build one of my gems (julien51-sax-machine), it seems related to another gem's gemspec.</title>
632
+ <description>julien51: @github : I get an error when trying to build one of my gems (julien51-sax-machine), it seems related to another gem's gemspec.</description>
633
+ <pubDate>Thu, 30 Jul 2009 01:00:30 +0000</pubDate>
634
+ <guid>http://twitter.com/julien51/statuses/2920716033</guid>
635
+ <link>http://twitter.com/julien51/statuses/2920716033</link>
636
+ </item>
637
+ <item>
638
+ <title>julien51: Hum, San Francisco's summer are delightful. http://bit.ly/VeXt4</title>
639
+ <description>julien51: Hum, San Francisco's summer are delightful. http://bit.ly/VeXt4</description>
640
+ <pubDate>Wed, 29 Jul 2009 23:07:32 +0000</pubDate>
641
+ <guid>http://twitter.com/julien51/statuses/2918869948</guid>
642
+ <link>http://twitter.com/julien51/statuses/2918869948</link>
643
+ </item>
644
+ </channel>
645
+ </rss>
646
+ eoxml
647
+ end
648
+ it "should parse the title" do
649
+ @document.title.should == 'Twitter / julien51'
650
+ end
651
+
652
+ it "should find an entry" do
653
+ @document.entries.length.should == 2
654
+ end
655
+ end
438
656
  end
439
657
  end
metadata CHANGED
@@ -1,10 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: julien51-sax-machine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.16
4
+ version: 0.0.22
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul Dix
8
+ - astro
9
+ - julien51
10
+ - superfeedr
8
11
  autorequire:
9
12
  bindir: bin
10
13
  cert_chain: []
@@ -38,14 +41,16 @@ files:
38
41
  - lib/sax-machine/sax_document.rb
39
42
  - lib/sax-machine/sax_handler.rb
40
43
  - lib/sax-machine/ns_stack.rb
44
+ - lib/sax-machine/sax_event_recorder.rb
41
45
  - README.textile
42
46
  - Rakefile
43
47
  - spec/spec.opts
44
48
  - spec/spec_helper.rb
45
49
  - spec/sax-machine/sax_document_spec.rb
46
- has_rdoc: false
47
- homepage: http://github.com/pauldix/sax-machine
48
- licenses:
50
+ has_rdoc: true
51
+ homepage: http://github.com/julien51/sax-machine
52
+ licenses: []
53
+
49
54
  post_install_message:
50
55
  rdoc_options: []
51
56