naws_xml 0.1.0.pre

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 43f5d536a660673e5bdbddcc9a7a3c6ee25937ab
4
+ data.tar.gz: ac5592fd8c834de4f138d35367c35a61886bfb71
5
+ SHA512:
6
+ metadata.gz: b3ea0cbc23601fc28ae76bfafb7b5be4b204933eb6eca767c714dc4ce18795999e9a8a58362cd80a005d857346ad511f38103d770840bf1faee5a85d320bf16f
7
+ data.tar.gz: 5b652f12ba158ec6f1e4b68132f5d4cbd115a3b39c581d34422aa2d7a13e8d4c3fb3c89dabf56c8047781f2c371ea4bee0e2b5ca9790b7a08bbcd2e16649779e
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ox'
4
+
5
+ module NawsXml
6
+ # @api private
7
+ module Engines
8
+ # @api private
9
+ class OxEngine
10
+
11
+ def parse(xml)
12
+ doc = Ox.parse(xml)
13
+ doc = doc.nodes.first if doc.is_a?(Ox::Document)
14
+ raise "no XML element found: #{xml.inspect}" unless doc
15
+ parse_node(doc)
16
+ rescue StandardError => error
17
+ raise ParseError.new(error)
18
+ end
19
+
20
+ def self.parse(xml)
21
+ new.parse(xml)
22
+ end
23
+
24
+ private
25
+
26
+ def parse_node(ox_node)
27
+ node = Node.new(ox_node.name)
28
+ ox_node.attributes.each_pair do |key, value|
29
+ node.attributes[key.to_s] = value
30
+ end
31
+ parse_node_content(node, ox_node)
32
+ node
33
+ end
34
+
35
+ def parse_node_content(node, ox_node)
36
+ ox_node.nodes.each do |value|
37
+ if value.is_a?(String)
38
+ node << value
39
+ else
40
+ node << parse_node(value)
41
+ end
42
+ end
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rexml/document'
4
+
5
+ module NawsXml
6
+ # @api private
7
+ module Engines
8
+ # @api private
9
+ class RexmlEngine
10
+
11
+ def parse(xml)
12
+ doc = REXML::Document.new(xml.strip)
13
+ raise "no XML element found: #{xml.inspect}" unless doc.root
14
+ parse_node(doc.root)
15
+ rescue StandardError => error
16
+ raise ParseError.new(error)
17
+ end
18
+
19
+ def self.parse(xml)
20
+ new.parse(xml)
21
+ end
22
+
23
+ private
24
+
25
+ def parse_node(rexml_node)
26
+ node = Node.new(rexml_node.name)
27
+ apply_attributes(node, rexml_node)
28
+ apply_text(node, rexml_node)
29
+ apply_child_nodes(node, rexml_node)
30
+ node
31
+ end
32
+
33
+ def apply_attributes(node, rexml_node)
34
+ rexml_node.attributes.each_attribute do |attr|
35
+ node.attributes[attr.name] = attr.value
36
+ end
37
+ end
38
+
39
+ def apply_text(node, rexml_node)
40
+ text = rexml_node.text
41
+ return if text.nil?
42
+ return if text.strip.empty?
43
+ node << text
44
+ end
45
+
46
+ def apply_child_nodes(node, rexml_node)
47
+ rexml_node.each_element do |value|
48
+ node.append(parse_node(value))
49
+ end
50
+ end
51
+
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'stringio'
4
+
5
+ module NawsXml
6
+ class Formatter
7
+
8
+ NEGATIVE_INDENT = 'indent must be greater than or equal to zero'
9
+
10
+ # @param [String] indent
11
+ # When `indent` is non-empty whitespace, then the XML will
12
+ # be pretty-formatted with each level of nodes indented by
13
+ # `indent`.
14
+ # @raise [ArgumentError] when indent is not a String.
15
+ def initialize(indent: '')
16
+ if !indent.is_a?(String)
17
+ raise ArgumentError, "expected a String, got #{indent.class}"
18
+ end
19
+ @indent = indent
20
+ @eol = indent.empty? ? '' : "\n"
21
+ end
22
+
23
+ # @param [Node] node
24
+ # @return [String<XML>]
25
+ def format(node)
26
+ buffer = StringIO.new
27
+ serialize(buffer, node, '')
28
+ buffer.string
29
+ end
30
+
31
+ private
32
+
33
+ def serialize(buffer, node, pad)
34
+ return buffer.write(self_close_node(node, pad)) if node.empty?
35
+ return buffer.write(text_node(node, pad)) if node.text
36
+ serialize_nested(buffer, node, pad)
37
+ end
38
+
39
+ def self_close_node(node, pad)
40
+ "#{pad}<#{node.name}#{attrs(node)}/>#{@eol}"
41
+ end
42
+
43
+ def text_node(node, pad)
44
+ "#{pad}<#{node.name}#{attrs(node)}>#{node.text.encode(xml: :text)}</#{node.name}>#{@eol}"
45
+ end
46
+
47
+ def serialize_nested(buffer, node, pad)
48
+ buffer.write("#{pad}<#{node.name}#{attrs(node)}>#{@eol}")
49
+ nested_pad = "#{pad}#{@indent}"
50
+ node.child_nodes.each do |child_node|
51
+ serialize(buffer, child_node, nested_pad)
52
+ end
53
+ buffer.write("#{pad}</#{node.name}>#{@eol}")
54
+ end
55
+
56
+ def attrs(node)
57
+ node.attributes.map do |key, value|
58
+ " #{key}=#{value.to_s.encode(xml: :attr)}"
59
+ end.join
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NawsXml
4
+ class Node
5
+
6
+ # @api private
7
+ BOTH_TYPES = 'Nodes may not have both text and child nodes'
8
+
9
+ # @param [String] name
10
+ def initialize(name, *children)
11
+ @name = name
12
+ @attributes = {}
13
+ @child_nodes = []
14
+ @child_node_map = {}
15
+ @text = []
16
+ append(*children)
17
+ end
18
+
19
+ # @return [String]
20
+ attr_reader :name
21
+
22
+ # @return [Hash<String, String>]
23
+ attr_reader :attributes
24
+
25
+ # @return [Array<Node, String>]
26
+ attr_reader :children
27
+
28
+ def append(*children)
29
+ children.flatten.each do |child|
30
+ case child
31
+ when Node
32
+ raise ArgumentError, BOTH_TYPES unless @text.empty?
33
+ @child_nodes << child
34
+ @child_node_map[child.name] ||= []
35
+ @child_node_map[child.name] << child
36
+ when String
37
+ raise ArgumentError, BOTH_TYPES unless @child_nodes.empty?
38
+ @text << child
39
+ else
40
+ raise ArgumentError, 'expected NawsXml::Node or String, ' \
41
+ "got #{child.class}"
42
+ end
43
+ end
44
+ end
45
+ alias << append
46
+
47
+ # @return [String, nil]
48
+ def text
49
+ @text.empty? ? nil : @text.join('')
50
+ end
51
+
52
+ # @param [String] node_name
53
+ # @return [Array<Node>]
54
+ def [](node_name)
55
+ children(node_name)
56
+ end
57
+
58
+ # @overload children()
59
+ # @return [Array<Node>] Returns an array of all child nodes.
60
+ # @overload children(child_name)
61
+ # @param [String] child_name
62
+ # @return [Array<Node>] Returns an array of child nodes with the given name.
63
+ def children(*args)
64
+ nodes =
65
+ case args.count
66
+ when 0 then @child_nodes
67
+ when 1 then @child_node_map.fetch(args.first, [])
68
+ else raise ArgumentError, "expected 0 or 1 arguments"
69
+ end
70
+ yield(nodes) if block_given? && !nodes.empty?
71
+ nodes
72
+ end
73
+ alias child_nodes children
74
+
75
+ # @return [Boolean]
76
+ def empty?
77
+ @child_nodes.empty? && @text.empty?
78
+ end
79
+
80
+ # @return [Array<String>]
81
+ def child_node_names
82
+ @child_node_map.keys
83
+ end
84
+
85
+ # @return [Boolean]
86
+ def child_node?(name)
87
+ @child_node_map.key?(name)
88
+ end
89
+
90
+ # @yield [child]
91
+ # @yieldparam child [Node]
92
+ # @return [Node, nil]
93
+ def child(child_name)
94
+ child = @child_node_map[child_name].first
95
+ yield(child) if block_given? && !child.nil?
96
+ child
97
+ end
98
+ alias at child
99
+
100
+ # @yield [text]
101
+ # @yieldparam text [String]
102
+ # @return [String, nil]
103
+ def text_at(child_name)
104
+ text = @child_node_map[child_name].first&.text
105
+ yield(text) if block_given? && !text.nil?
106
+ text
107
+ end
108
+
109
+ # @option options [String] :indent ('')
110
+ # When `indent` is non-empty whitespace, then the XML will
111
+ # be pretty-formatted with each level of nodes indented by
112
+ # `indent`.
113
+ # @return [String<XML>]
114
+ def to_xml(indent: '')
115
+ Formatter.new(indent: indent).format(self)
116
+ end
117
+ alias to_str to_xml
118
+ alias to_s to_xml
119
+
120
+ end
121
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NawsXml
4
+ class NodeParser
5
+
6
+ # @param [NawsXml::Node, String<XML>] xml
7
+ def initialize(xml:)
8
+ @node = xml.is_a?(NawsXml::Node) ? xml : Parser.parse(xml)
9
+ @data = {}
10
+ end
11
+
12
+ # @return [Hash]
13
+ attr_reader :data
14
+
15
+ def parse_string(node_name:, key:)
16
+ node = @node[node_name].first
17
+ return if node.nil?
18
+ @data[key] = string_value(node)
19
+ end
20
+
21
+ def parse_flat_list(node_name:, key:)
22
+ @data[key] = []
23
+ @node[node_name].each do |node|
24
+ # need to support non-flat lists
25
+ # need to support multiple formats, Time.parse, Float(), etc
26
+ # need to support nested nodes
27
+ yield
28
+ @data[key] << node.text.to_s
29
+ end
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NawsXml
4
+ class ParseError < StandardError
5
+
6
+ def initialize(error_or_message)
7
+ if error_or_message.is_a?(StandardError)
8
+ @original_error = error_or_message
9
+ super(error_or_message.message)
10
+ else
11
+ super(error_or_message)
12
+ end
13
+ end
14
+
15
+ # @return [StandardError, nil]
16
+ attr_reader :original_error
17
+
18
+ end
19
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NawsXml
4
+ # @api private
5
+ class Parser
6
+
7
+ def initialize(engine: nil)
8
+ @engine = engine || Parser.default_engine
9
+ end
10
+
11
+ # @param [String<XML>] xml
12
+ # @return [Node]
13
+ def parse(xml)
14
+ @engine.parse(xml)
15
+ end
16
+
17
+ class << self
18
+
19
+ def parse(xml)
20
+ new(engine: default_engine).parse(xml)
21
+ end
22
+
23
+ def set_default_engine(engine)
24
+ @engine = engine
25
+ end
26
+
27
+ def default_engine
28
+ @engine
29
+ end
30
+
31
+ def ox_engine
32
+ require_relative 'engines/ox_engine'
33
+ Engines::OxEngine.new
34
+ end
35
+
36
+ def rexml_engine
37
+ require_relative 'engines/rexml_engine'
38
+ Engines::RexmlEngine.new
39
+ end
40
+
41
+ end
42
+
43
+ begin
44
+ set_default_engine(ox_engine)
45
+ rescue LoadError
46
+ set_default_engine(rexml_engine)
47
+ end
48
+
49
+ end
50
+ end
data/lib/naws_xml.rb ADDED
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'naws_xml/formatter'
4
+ require_relative 'naws_xml/node'
5
+ require_relative 'naws_xml/parse_error'
6
+ require_relative 'naws_xml/parser'
7
+
8
+ # NawsXml is a purpose-built set of utilities for working with
9
+ # XML. It does not support many/most features of generic XML
10
+ # parsing and serialization.
11
+ # @api private
12
+ module NawsXml
13
+ class << self
14
+
15
+ # @param [String] xml
16
+ # @return [Node]
17
+ def parse(xml, engine: Parser.default_engine)
18
+ Parser.new(engine: engine).parse(xml)
19
+ end
20
+
21
+ end
22
+ end
metadata ADDED
@@ -0,0 +1,199 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: naws_xml
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0.pre
5
+ platform: ruby
6
+ authors:
7
+ - The Incognito Coder
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-11-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ox
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: coveralls
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.8'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.8'
41
+ - !ruby/object:Gem::Dependency
42
+ name: kramdown
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.16'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.16'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.11'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.11'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '12.3'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '12.3'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.7'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.7'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.56'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.56'
111
+ - !ruby/object:Gem::Dependency
112
+ name: semver-string
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '1.0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '1.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: yard
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '0.9'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '0.9'
139
+ - !ruby/object:Gem::Dependency
140
+ name: yard-sitemap
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '1.0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '1.0'
153
+ description:
154
+ email:
155
+ - theincognitocoder@gmail.com
156
+ executables: []
157
+ extensions: []
158
+ extra_rdoc_files: []
159
+ files:
160
+ - lib/naws_xml.rb
161
+ - lib/naws_xml/engines/ox_engine.rb
162
+ - lib/naws_xml/engines/rexml_engine.rb
163
+ - lib/naws_xml/formatter.rb
164
+ - lib/naws_xml/node.rb
165
+ - lib/naws_xml/node_parser.rb
166
+ - lib/naws_xml/parse_error.rb
167
+ - lib/naws_xml/parser.rb
168
+ homepage: https://github.com/theincognitocoder/naws_xml
169
+ licenses:
170
+ - MIT
171
+ metadata:
172
+ bug_tracker_uri: https://github.com/theincognitocoder/naws_xml/issues
173
+ changelog_uri: https://github.com/theincognitocoder/naws_xml/blob/master/CHANGELOG.md
174
+ documentation_uri: https://www.rubydoc.info/github/theincognitocoder/naws_xml/master
175
+ homepage_uri: https://github.com/theincognitocoder/naws_xml
176
+ mailing_list_uri: https://gitter.im/theincognitocoder
177
+ source_code_uri: https://github.com/theincognitocoder/naws_xml
178
+ wiki_uri: https://github.com/theincognitocoder/naws_xml/wiki
179
+ post_install_message:
180
+ rdoc_options: []
181
+ require_paths:
182
+ - lib
183
+ required_ruby_version: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ required_rubygems_version: !ruby/object:Gem::Requirement
189
+ requirements:
190
+ - - ">"
191
+ - !ruby/object:Gem::Version
192
+ version: 1.3.1
193
+ requirements: []
194
+ rubyforge_project:
195
+ rubygems_version: 2.5.1
196
+ signing_key:
197
+ specification_version: 4
198
+ summary: Naws Xml
199
+ test_files: []