flexparser 1.0.2
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.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +22 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/Guardfile +42 -0
- data/LICENSE +674 -0
- data/README.md +200 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/flexparser.gemspec +32 -0
- data/lib/flexparser.rb +37 -0
- data/lib/flexparser/anonymous_parser.rb +20 -0
- data/lib/flexparser/class_methods.rb +94 -0
- data/lib/flexparser/collection_parser.rb +27 -0
- data/lib/flexparser/configuration.rb +23 -0
- data/lib/flexparser/empty_fragment.rb +23 -0
- data/lib/flexparser/errors.rb +21 -0
- data/lib/flexparser/fragment.rb +44 -0
- data/lib/flexparser/fragment_builder.rb +15 -0
- data/lib/flexparser/tag_parser.rb +65 -0
- data/lib/flexparser/version.rb +3 -0
- data/lib/flexparser/xpaths.rb +91 -0
- data/test.xml +11 -0
- metadata +181 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
module Flexparser
|
2
|
+
#
|
3
|
+
# Class to keep config-options for the whole module.
|
4
|
+
#
|
5
|
+
class Configuration
|
6
|
+
AVAILABLE_OPTIONS =
|
7
|
+
%i[retry_without_namespaces explicit_property_naming].freeze
|
8
|
+
attr_accessor(*AVAILABLE_OPTIONS)
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
set_defaults
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_h
|
15
|
+
Hash[AVAILABLE_OPTIONS.map { |o| [o, public_send(o)] }]
|
16
|
+
end
|
17
|
+
|
18
|
+
def set_defaults
|
19
|
+
@retry_without_namespaces = true
|
20
|
+
@explicit_property_naming = true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Flexparser
|
2
|
+
#
|
3
|
+
# Used as a safeguard when passing nil or an empty string
|
4
|
+
# to the FragmentBuilder.
|
5
|
+
#
|
6
|
+
class EmptyFragment < Fragment
|
7
|
+
def xpath(_path)
|
8
|
+
self.class.new(nil)
|
9
|
+
end
|
10
|
+
|
11
|
+
def empty?
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
def text
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def namespaces
|
20
|
+
{}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Flexparser
|
2
|
+
class Error < StandardError; end
|
3
|
+
|
4
|
+
#
|
5
|
+
# An Error that will be thrown if a value is required but
|
6
|
+
# missing.
|
7
|
+
#
|
8
|
+
class RequiredMissingError < Error
|
9
|
+
attr_reader :parser
|
10
|
+
|
11
|
+
def initialize(parser)
|
12
|
+
@parser = parser
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
#
|
17
|
+
# An Error that is thrown when there is no clear name
|
18
|
+
# for a property.
|
19
|
+
#
|
20
|
+
class AmbiguousNamingError < ArgumentError; end
|
21
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Flexparser
|
2
|
+
#
|
3
|
+
# Wraps Nokogiri::Xml::Document to automatically hand down namespaces
|
4
|
+
# to fragments generated by #xpath
|
5
|
+
#
|
6
|
+
class Fragment
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
attr_reader :doc
|
10
|
+
attr_writer :namespaces
|
11
|
+
|
12
|
+
def_delegators(:@doc, :text, :empty?, :map,
|
13
|
+
:each, :namespaces, :collect_namespaces)
|
14
|
+
|
15
|
+
def initialize(str, namespaces: {})
|
16
|
+
@doc = str.is_a?(String) ? Nokogiri::XML(str) : str
|
17
|
+
@propagated_namespaces = namespaces
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
"#{self.class}: \n\tNamespaces: #{_namespaces}
|
22
|
+
\tRaw:\n\t\t#{@raw_doc}\tNokogiri:\n#{doc}"
|
23
|
+
end
|
24
|
+
|
25
|
+
#
|
26
|
+
# The same as Nokogiri::Xml::Document#xpath but
|
27
|
+
# namespaces are passed down to resulting fragments.
|
28
|
+
#
|
29
|
+
def xpath(path)
|
30
|
+
FragmentBuilder
|
31
|
+
.build(doc.xpath(path, propagating_namespaces))
|
32
|
+
end
|
33
|
+
|
34
|
+
def propagating_namespaces
|
35
|
+
return @propagated_namespaces unless doc.respond_to?(:namespaces)
|
36
|
+
if doc.respond_to?(:collect_namespaces)
|
37
|
+
return @propagated_namespaces.merge doc.collect_namespaces
|
38
|
+
end
|
39
|
+
@propagated_namespaces.merge doc.namespaces
|
40
|
+
end
|
41
|
+
|
42
|
+
alias pns propagating_namespaces
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Flexparser
|
2
|
+
#
|
3
|
+
# Class to build handle turn a given object into
|
4
|
+
# a Fragment. Used mostly as a Safeguard.
|
5
|
+
#
|
6
|
+
class FragmentBuilder
|
7
|
+
class << self
|
8
|
+
def build(str, namespaces: {})
|
9
|
+
return str if str.is_a?(Fragment)
|
10
|
+
return EmptyFragment.new(str) if str.nil?
|
11
|
+
Fragment.new(str, namespaces: namespaces)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Flexparser
|
4
|
+
#
|
5
|
+
# A parser for a single tag.
|
6
|
+
#
|
7
|
+
class TagParser
|
8
|
+
attr_accessor :xpaths, :options
|
9
|
+
|
10
|
+
#
|
11
|
+
# @param name [Symbol] the name used for the accessor
|
12
|
+
# defined on the parent.
|
13
|
+
# @param xpath [String] an xpath string used to access a Nokogiri-Fragment
|
14
|
+
#
|
15
|
+
def initialize(tags, **opts)
|
16
|
+
@xpaths = XPaths.new(tags)
|
17
|
+
@options = opts
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# @param doc [Nokogiri::Node] a node that can be accessed through xpath
|
22
|
+
# @return a String if no type was specified, otherwise the type
|
23
|
+
# will try to parse the string using ::parse
|
24
|
+
#
|
25
|
+
def parse(doc)
|
26
|
+
result = content(doc) || return
|
27
|
+
return options[:sub_parser].parse(result) if sub_parser
|
28
|
+
type ? transform(result.text) : result.text
|
29
|
+
end
|
30
|
+
|
31
|
+
def name
|
32
|
+
options[:name] || xpaths.method_name
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
def sub_parser
|
38
|
+
options[:sub_parser]
|
39
|
+
end
|
40
|
+
|
41
|
+
def type
|
42
|
+
options[:type] || options[:transform]
|
43
|
+
end
|
44
|
+
|
45
|
+
def transform(string)
|
46
|
+
return options[:type].parse string if options[:type]
|
47
|
+
return string.public_send(options[:transform]) if options[:transform]
|
48
|
+
string
|
49
|
+
end
|
50
|
+
|
51
|
+
def content(doc)
|
52
|
+
xpaths.valid_paths(doc).each do |path|
|
53
|
+
set = doc.xpath("(#{path})[1]")
|
54
|
+
return set unless set.empty?
|
55
|
+
end
|
56
|
+
return unless options[:required]
|
57
|
+
raise_required_error(doc)
|
58
|
+
end
|
59
|
+
|
60
|
+
def raise_required_error(doc)
|
61
|
+
raise(RequiredMissingError.new(self),
|
62
|
+
"Required field #{name} not found in #{doc}")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Flexparser
|
4
|
+
#
|
5
|
+
# A class to represent a collection of paths.
|
6
|
+
# This class is supposed to be more convenient for
|
7
|
+
# handeling a collection of xpaths.
|
8
|
+
#
|
9
|
+
class XPaths
|
10
|
+
attr_accessor :tags
|
11
|
+
|
12
|
+
#
|
13
|
+
# @param tags [Array<String>] a list of names for xpaths
|
14
|
+
# that the xpaths instance will handle.
|
15
|
+
#
|
16
|
+
def initialize(tags)
|
17
|
+
@tags = Array(tags).map(&:to_sym).flatten
|
18
|
+
raise ArgumentError, arg_err_msg if @tags.empty?
|
19
|
+
end
|
20
|
+
|
21
|
+
#
|
22
|
+
# The name of an accesor, based on the collection of tags.
|
23
|
+
#
|
24
|
+
def method_name
|
25
|
+
tags.first.to_s.sub(/^@/, '').gsub(/([[:punct:]]|-)+/, '_')
|
26
|
+
end
|
27
|
+
|
28
|
+
#
|
29
|
+
# Builds xpaths from the given tags
|
30
|
+
#
|
31
|
+
def xpaths
|
32
|
+
@xpaths ||= begin
|
33
|
+
paths = tags.map do |t|
|
34
|
+
XPath.current.descendant(t)
|
35
|
+
end
|
36
|
+
if Flexparser.configuration.retry_without_namespaces
|
37
|
+
paths += ns_ignortant_xpaths
|
38
|
+
end
|
39
|
+
paths
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
#
|
44
|
+
# Returns the valid paths from this collection, based on the given doc.
|
45
|
+
# @param doc [Flexparser::Fragment] the fragment
|
46
|
+
# that carries the namespaces.
|
47
|
+
# @return [Array<String>] the xpaths that can be applied to this fragment
|
48
|
+
#
|
49
|
+
def valid_paths(doc)
|
50
|
+
xpaths.reject do |path|
|
51
|
+
namespaced?(path) && !namespace_available?(path, doc)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
#
|
58
|
+
# Returns xpaths that ignore namespaces.
|
59
|
+
#
|
60
|
+
def ns_ignortant_xpaths
|
61
|
+
tags.reject(&method(:namespaced?)).flat_map do |tag|
|
62
|
+
XPath.current.descendant[XPath.name.equals(tag.to_s)]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
# Checks whether a tag has a specified namespace.
|
68
|
+
# @param tag [String] The tag to be looked at
|
69
|
+
#
|
70
|
+
def namespaced?(tag)
|
71
|
+
!(tag.to_s =~ /\w+:\w+/).nil?
|
72
|
+
end
|
73
|
+
|
74
|
+
#
|
75
|
+
# Checks whether or not a path is can be applied to a given fragment.
|
76
|
+
# @param path [String] the path that is to be checked
|
77
|
+
# @param doc [Flexparser::Fragment] the fragment that carries the
|
78
|
+
# namespaces.
|
79
|
+
# @return [Boolean] true if the path can be applied, false otherwise.
|
80
|
+
#
|
81
|
+
def namespace_available?(path, doc)
|
82
|
+
nms = doc.propagating_namespaces.keys.map { |ns| ns.gsub('xmlns:', '') }
|
83
|
+
path.to_s.match Regexp.union(nms)
|
84
|
+
end
|
85
|
+
|
86
|
+
# no-doc
|
87
|
+
def arg_err_msg
|
88
|
+
'There needs to be at least one path for a path-collection to be valid'
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/test.xml
ADDED
metadata
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: flexparser
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Paul Martensen
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-08-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: nokogiri
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.7'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: xpath
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.1'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: guard
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.14'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.14'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: guard-minitest
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.4'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '2.4'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pry-byebug
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.4'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.4'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: bundler
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.13'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.13'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rake
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '10.0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '10.0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: minitest
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '5.0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '5.0'
|
125
|
+
description: A flexible and robust parser-dsl.
|
126
|
+
email:
|
127
|
+
- paul.martensen@gmx.de
|
128
|
+
executables: []
|
129
|
+
extensions: []
|
130
|
+
extra_rdoc_files: []
|
131
|
+
files:
|
132
|
+
- ".gitignore"
|
133
|
+
- ".rubocop.yml"
|
134
|
+
- ".rubocop_todo.yml"
|
135
|
+
- ".travis.yml"
|
136
|
+
- Gemfile
|
137
|
+
- Guardfile
|
138
|
+
- LICENSE
|
139
|
+
- README.md
|
140
|
+
- Rakefile
|
141
|
+
- bin/console
|
142
|
+
- bin/setup
|
143
|
+
- flexparser.gemspec
|
144
|
+
- lib/flexparser.rb
|
145
|
+
- lib/flexparser/anonymous_parser.rb
|
146
|
+
- lib/flexparser/class_methods.rb
|
147
|
+
- lib/flexparser/collection_parser.rb
|
148
|
+
- lib/flexparser/configuration.rb
|
149
|
+
- lib/flexparser/empty_fragment.rb
|
150
|
+
- lib/flexparser/errors.rb
|
151
|
+
- lib/flexparser/fragment.rb
|
152
|
+
- lib/flexparser/fragment_builder.rb
|
153
|
+
- lib/flexparser/tag_parser.rb
|
154
|
+
- lib/flexparser/version.rb
|
155
|
+
- lib/flexparser/xpaths.rb
|
156
|
+
- test.xml
|
157
|
+
homepage: https://github.com/lokalportal/flexparser
|
158
|
+
licenses:
|
159
|
+
- GPL-3.0
|
160
|
+
metadata: {}
|
161
|
+
post_install_message:
|
162
|
+
rdoc_options: []
|
163
|
+
require_paths:
|
164
|
+
- lib
|
165
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
166
|
+
requirements:
|
167
|
+
- - ">="
|
168
|
+
- !ruby/object:Gem::Version
|
169
|
+
version: '0'
|
170
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
171
|
+
requirements:
|
172
|
+
- - ">="
|
173
|
+
- !ruby/object:Gem::Version
|
174
|
+
version: '0'
|
175
|
+
requirements: []
|
176
|
+
rubyforge_project:
|
177
|
+
rubygems_version: 2.6.12
|
178
|
+
signing_key:
|
179
|
+
specification_version: 4
|
180
|
+
summary: A xml-parser dsl
|
181
|
+
test_files: []
|