sxrb 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,93 @@
1
+ require 'libxml'
2
+ require 'sxrb/node'
3
+ require 'sxrb/text_node'
4
+
5
+ # This class provides callbacks for SAX API which are configured with sxrb DSL.
6
+
7
+ module SXRB
8
+ class Callbacks
9
+ #include LibXML::XML::SaxParser::VerboseCallbacks
10
+ include LibXML::XML::SaxParser::Callbacks
11
+ def initialize
12
+ @stack = []
13
+ @rules_map = Hash.new {|h,k| h[k] = Rules.new}
14
+ end
15
+
16
+ def on_start_element_ns(name, attributes, prefix, uri, namespaces)
17
+ Node.new(name, attributes, prefix, uri, namespaces).tap do |node|
18
+ @stack.push(node)
19
+ invoke_callback(:start, node)
20
+ @current_element.append node if @current_element
21
+ @current_element = node if current_matching_rules.any?(&:recursive) || @current_element
22
+ end
23
+ end
24
+
25
+ def on_characters(chars)
26
+ if @stack.last.is_a? TextNode
27
+ @stack.last.append_text chars
28
+ else
29
+ TextNode.new(chars).tap do |node|
30
+ invoke_callback(:characters, node)
31
+ @stack.push node
32
+ @current_element.append node if @current_element
33
+ end
34
+ end
35
+ end
36
+
37
+ def on_end_element_ns(name, prefix, uri)
38
+ @stack.pop if @stack.last.is_a? TextNode
39
+
40
+ if @current_element
41
+ invoke_callback(:element, @current_element) if current_matching_rules.any?(&:recursive)
42
+ @current_element = @current_element.parent
43
+ end
44
+
45
+ invoke_callback(:end, @stack.last)
46
+ @stack.pop.tap do |node|
47
+ raise SXRB::TagMismatchError if node.name != name
48
+ end
49
+ end
50
+
51
+ def add_callback(type, rule_path, &block)
52
+ @rules_map[Regexp.new "^#{rule_path}$"].tap do |rules|
53
+ rules.rules[type] = block
54
+ rules.recursive = (type == :element) # true / false
55
+ end
56
+ end
57
+
58
+ # options:
59
+ # recursive
60
+ #
61
+ def add_rule(rule, rule_path, options)
62
+ operator = options[:recursive] ? '.*' : ' '
63
+ new_rule = rule_path + operator + rule
64
+ new_rule.strip
65
+ end
66
+
67
+ private
68
+
69
+ def invoke_callback(type, *args)
70
+ current_matching_rules.
71
+ map {|value| value.rules[type]}.
72
+ compact.each {|callback| callback.call(*args)}
73
+ end
74
+
75
+ def current_matching_rules
76
+ @rules_map.each_pair.
77
+ select {|rule, value| current_rule_path =~ rule}.
78
+ map {|rule, value| value}
79
+ end
80
+
81
+ def current_rule_path
82
+ @stack.map(&:name).join(' ')
83
+ end
84
+ end
85
+
86
+ class Rules
87
+ attr_accessor :rules, :recursive
88
+ def initialize
89
+ @rules = {}
90
+ @recursive = false
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,3 @@
1
+ module SXRB
2
+ class TagMismatchError < StandardError; end
3
+ end
data/lib/sxrb/node.rb ADDED
@@ -0,0 +1,14 @@
1
+ module SXRB
2
+ class Node < Struct.new(:name, :attributes, :prefix, :uri, :namespaces, :parent)
3
+ attr_accessor :children
4
+ def initialize(*args, &block)
5
+ super(*args, &block)
6
+ @children = []
7
+ end
8
+
9
+ def append(node)
10
+ node.parent = self
11
+ @children << node
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,25 @@
1
+ require 'sxrb/callbacks'
2
+ require 'sxrb/proxy'
3
+
4
+ module SXRB
5
+ class Parser
6
+ def initialize(input, opts = {}, &block)
7
+ raise ArgumentError.new("Block expected") if block.nil?
8
+ options = {:mode => :string}.merge(opts)
9
+
10
+ # Create parser according to options
11
+
12
+ parser = case options[:mode]
13
+ when :string
14
+ LibXML::XML::SaxParser.string(input)
15
+ end
16
+
17
+ callbacks = Callbacks.new.tap do |cb|
18
+ yield Proxy.new(cb)
19
+ end
20
+
21
+ parser.callbacks = callbacks
22
+ parser.parse
23
+ end
24
+ end
25
+ end
data/lib/sxrb/proxy.rb ADDED
@@ -0,0 +1,41 @@
1
+ module SXRB
2
+ class Proxy
3
+ def initialize(callback_tree, current_path = '')
4
+ @callback_tree = callback_tree
5
+ @current_path = current_path
6
+ end
7
+
8
+ def child(*args, &block)
9
+ args.each do |tag|
10
+ @callback_tree.add_rule(tag, @current_path, :recursive => false).tap do |new_path|
11
+ block.call(Proxy.new(@callback_tree, new_path))
12
+ end
13
+ end
14
+ end
15
+
16
+ def descendant(*args, &block)
17
+ args.each do |tag|
18
+ @callback_tree.add_rule(tag, @current_path, :recursive => true).tap do |new_path|
19
+ block.call(Proxy.new(@callback_tree, new_path))
20
+ end
21
+ end
22
+ end
23
+
24
+ def on_element(&block)
25
+ @callback_tree.add_callback(:element, @current_path, &block)
26
+ end
27
+
28
+ def on_start(&block)
29
+ @callback_tree.add_callback(:start, @current_path, &block)
30
+ end
31
+
32
+ def on_characters(&block)
33
+ @callback_tree.add_callback(:characters, @current_path, &block)
34
+ end
35
+
36
+ def on_end(&block)
37
+ @callback_tree.add_callback(:end, @current_path, &block)
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,15 @@
1
+ module SXRB
2
+ class TextNode < Node
3
+ def initialize(text)
4
+ @text = text || ""
5
+ end
6
+
7
+ def append_text(text)
8
+ @text << text
9
+ end
10
+
11
+ def text
12
+ @text
13
+ end
14
+ end
15
+ end
data/lib/sxrb.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'sxrb/parser'
2
+ require 'sxrb/errors'
3
+
4
+ module SXRB; end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sxrb
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Bartek Borkowski
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-08-05 00:00:00 Z
19
+ dependencies: []
20
+
21
+ description: Robust XML parser that allows defining desired behavior with fancy DSL
22
+ email:
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files: []
28
+
29
+ files:
30
+ - lib/sxrb/text_node.rb
31
+ - lib/sxrb/callbacks.rb
32
+ - lib/sxrb/node.rb
33
+ - lib/sxrb/parser.rb
34
+ - lib/sxrb/errors.rb
35
+ - lib/sxrb/proxy.rb
36
+ - lib/sxrb.rb
37
+ homepage: http://rubygems.org/gems/sxrb
38
+ licenses: []
39
+
40
+ post_install_message:
41
+ rdoc_options: []
42
+
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ none: false
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ hash: 3
51
+ segments:
52
+ - 0
53
+ version: "0"
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ hash: 3
60
+ segments:
61
+ - 0
62
+ version: "0"
63
+ requirements: []
64
+
65
+ rubyforge_project:
66
+ rubygems_version: 1.8.24
67
+ signing_key:
68
+ specification_version: 3
69
+ summary: Smart XML parser
70
+ test_files: []
71
+