body_party 0.0.1.pre
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/lib/body_party.rb +9 -0
- data/lib/document.rb +119 -0
- data/lib/hash_parser.rb +86 -0
- data/lib/node.rb +22 -0
- data/lib/version.rb +1 -0
- data/lib/xpath_element.rb +24 -0
- data/lib/xpath_parser.rb +29 -0
- metadata +69 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 0a12945e49641b602d97e1bf92abfa81a166ee804c56b639919d76616f7e2abf
|
4
|
+
data.tar.gz: dcacbde51918ea152544024f82decbb3accb8e865a862c10dc4e08d6ccb3b7c9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4e7a2416b3bcfa2da82278d515537c74792827f937074ce5851eb88c651e12dcf8daa750f2f95b69eb6a012c6cbb2bf89ae6cde6152c943996c88e683f861cfb
|
7
|
+
data.tar.gz: 4dfa60179cf4fd825a8eb3c490d925b913bfbb32a60b2ce13625710682d308d5434c5bf04f921c3f8d4cb69d82cbc2de45c45728abb7b9517123555204ebb84b
|
data/lib/body_party.rb
ADDED
data/lib/document.rb
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
module BodyParty
|
6
|
+
class Document
|
7
|
+
attr_accessor :xpaths, :doc, :type
|
8
|
+
|
9
|
+
def initialize(**args)
|
10
|
+
@xpaths = args.fetch(:xpaths)
|
11
|
+
@type = args.fetch(:type, :xml)
|
12
|
+
@doc = build_doc
|
13
|
+
end
|
14
|
+
|
15
|
+
def generate!
|
16
|
+
xpaths.each do |xpath|
|
17
|
+
xpath_parser = BodyParty::XpathParser.new(xpath)
|
18
|
+
node = create_node_from_xpath(xpath_parser)
|
19
|
+
doc << node unless doc.nodes.any? { |n| node.equal?(n) }
|
20
|
+
end
|
21
|
+
ox = Ox.dump(doc)
|
22
|
+
if type == :hash
|
23
|
+
StringIO.new(ox)
|
24
|
+
parser = HashParser.new
|
25
|
+
Ox.sax_parse(parser, ox)
|
26
|
+
parser.to_h
|
27
|
+
elsif type == :xml
|
28
|
+
ox
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def create_node_from_xpath(xpath)
|
33
|
+
root_node = find_or_create_root_node(xpath.root)
|
34
|
+
|
35
|
+
create_child_nodes(root_node, xpath.childrens)
|
36
|
+
root_node
|
37
|
+
end
|
38
|
+
|
39
|
+
# Find last possible parent within the childrens
|
40
|
+
def find_last_child(parent, childrens)
|
41
|
+
return parent if childrens.empty?
|
42
|
+
|
43
|
+
return parent if childrens.count == 1 # Last child will the one to be created
|
44
|
+
|
45
|
+
child_element = childrens.shift
|
46
|
+
|
47
|
+
child = locate_child(parent, child_element)
|
48
|
+
|
49
|
+
if child.nil?
|
50
|
+
childrens.unshift(child_element)
|
51
|
+
return parent
|
52
|
+
end
|
53
|
+
|
54
|
+
find_last_child(child, childrens)
|
55
|
+
end
|
56
|
+
|
57
|
+
def create_child_nodes(root, childrens)
|
58
|
+
return root if childrens.empty?
|
59
|
+
|
60
|
+
root = find_last_child(root, childrens)
|
61
|
+
|
62
|
+
if childrens.count <= 1
|
63
|
+
return root if childrens.empty?
|
64
|
+
|
65
|
+
last_node_name = childrens.shift
|
66
|
+
child = create_node(last_node_name)
|
67
|
+
root << child
|
68
|
+
return root
|
69
|
+
end
|
70
|
+
|
71
|
+
child_name = childrens.shift
|
72
|
+
child = create_node(child_name)
|
73
|
+
|
74
|
+
root << create_child_nodes(child, childrens)
|
75
|
+
end
|
76
|
+
|
77
|
+
def locate_child(parent, child)
|
78
|
+
child_attributes = child.attributes
|
79
|
+
child_name = child.name
|
80
|
+
|
81
|
+
return parent.locate(child_name).first if child_attributes.empty?
|
82
|
+
|
83
|
+
formatted_child_attributes =
|
84
|
+
child_attributes.map { |attr, value| "#{child_name}[@#{attr}=#{value}]" }
|
85
|
+
formatted_child_attributes.all? do |attr|
|
86
|
+
child = parent.locate(attr).last
|
87
|
+
return nil if child.nil?
|
88
|
+
return child if child&.attributes&.transform_keys(&:to_s).to_a == child_attributes
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def find_or_create_root_node(xpath)
|
93
|
+
locate_child(doc, xpath) || create_node(xpath)
|
94
|
+
end
|
95
|
+
|
96
|
+
def create_node(element)
|
97
|
+
Node.new(element).node
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.generate(**args)
|
101
|
+
types = %i[hash xml]
|
102
|
+
type = args.fetch(:type, :xml)
|
103
|
+
raise 'Format should be etiher :hash or :xml' unless types.include?(type)
|
104
|
+
|
105
|
+
xpaths = args.fetch(:xpaths)
|
106
|
+
new(xpaths: xpaths, type: type).generate!
|
107
|
+
end
|
108
|
+
|
109
|
+
def build_doc
|
110
|
+
doc = Ox::Document.new
|
111
|
+
instruct = Ox::Instruct.new(:xml)
|
112
|
+
|
113
|
+
instruct[:version] = '1.0'
|
114
|
+
instruct[:encoding] = 'UTF-8'
|
115
|
+
doc << instruct
|
116
|
+
doc
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
data/lib/hash_parser.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Thanks to https://github.com/xkwd/oxml
|
4
|
+
|
5
|
+
class HashParser < ::Ox::Sax
|
6
|
+
EMPTY_STR = ''
|
7
|
+
TRUE_STR = 'true'
|
8
|
+
FALSE_STR = 'false'
|
9
|
+
DATE_TIME = /^-?\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?$/
|
10
|
+
DATE = /^-?\d{4}-\d{2}-\d{2}(?:Z|[+-]\d{2}:?\d{2})?$/
|
11
|
+
TIME = /^\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?$/
|
12
|
+
|
13
|
+
def initialize(options = {})
|
14
|
+
@memo = {}
|
15
|
+
@arr = []
|
16
|
+
@map = {}
|
17
|
+
@name = nil
|
18
|
+
@last_attr = nil
|
19
|
+
@delete_namespace_attributes = options.fetch(:delete_namespace_attributes, false)
|
20
|
+
@advanced_typecasting = options.fetch(:advanced_typecasting, false)
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_h
|
24
|
+
@memo.to_h
|
25
|
+
end
|
26
|
+
|
27
|
+
def attr(name, str)
|
28
|
+
@last_attr = "#{name}:#{str}"
|
29
|
+
return if @delete_namespace_attributes
|
30
|
+
|
31
|
+
return if name == :version
|
32
|
+
return if name == :encoding
|
33
|
+
|
34
|
+
start_element(name)
|
35
|
+
text(str)
|
36
|
+
end_element(name)
|
37
|
+
end
|
38
|
+
|
39
|
+
def start_element(name)
|
40
|
+
@arr.push(@memo)
|
41
|
+
|
42
|
+
@name = @map[name] ||= name
|
43
|
+
|
44
|
+
@memo = {}
|
45
|
+
text(@memo)
|
46
|
+
end
|
47
|
+
|
48
|
+
def attrs_done
|
49
|
+
return unless @last_attr =~ /nil:true/
|
50
|
+
|
51
|
+
@last_attr = nil
|
52
|
+
@arr.last[@name] = nil
|
53
|
+
end
|
54
|
+
|
55
|
+
def end_element(_name)
|
56
|
+
@memo = @arr.pop
|
57
|
+
end
|
58
|
+
|
59
|
+
def text(value)
|
60
|
+
if @arr.last[@name].is_a?(Array)
|
61
|
+
@arr.last[@name].pop unless value == @memo
|
62
|
+
@arr.last[@name] << cast(value)
|
63
|
+
elsif @arr.last[@name] && value == @memo
|
64
|
+
@arr.last[@name] = [@arr.last[@name], value]
|
65
|
+
else
|
66
|
+
@arr.last[@name] = cast(value)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def cast(value)
|
73
|
+
return if value == EMPTY_STR
|
74
|
+
return value unless @advanced_typecasting
|
75
|
+
|
76
|
+
case value
|
77
|
+
when EMPTY_STR then nil
|
78
|
+
when TRUE_STR then true
|
79
|
+
when FALSE_STR then false
|
80
|
+
when DATE_TIME then DateTime.parse(value)
|
81
|
+
when DATE then Date.parse(value)
|
82
|
+
when TIME then Time.parse(value)
|
83
|
+
else value
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/lib/node.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BodyParty
|
4
|
+
Node = Struct.new(:xpath_element) do
|
5
|
+
attr_accessor :node
|
6
|
+
|
7
|
+
def initialize(xpath_element)
|
8
|
+
raise ArgumentError, "xpath_element can't be nil" if xpath_element.nil?
|
9
|
+
|
10
|
+
self.xpath_element = xpath_element
|
11
|
+
self.node = ox_node
|
12
|
+
end
|
13
|
+
|
14
|
+
def ox_node
|
15
|
+
node = Ox::Element.new(xpath_element.name)
|
16
|
+
node << xpath_element.value if xpath_element.value
|
17
|
+
|
18
|
+
xpath_element.attributes.each { |attr, value| node[attr.to_sym] = value }
|
19
|
+
node
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/version.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
VERSION = "0.0.1.pre"
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module BodyParty
|
2
|
+
class XpathElement
|
3
|
+
attr_accessor :element, :value
|
4
|
+
|
5
|
+
def initialize(element)
|
6
|
+
@element = element
|
7
|
+
end
|
8
|
+
def name
|
9
|
+
element.split(/\[|\?=/).first
|
10
|
+
end
|
11
|
+
|
12
|
+
def value
|
13
|
+
return '' unless element.include?('?=')
|
14
|
+
|
15
|
+
@value ||= element.split('?=').last
|
16
|
+
end
|
17
|
+
|
18
|
+
def attributes
|
19
|
+
return [] if element.nil?
|
20
|
+
|
21
|
+
element.scan(/@(\w+)=([^\s\]]+)/)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/xpath_parser.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BodyParty
|
4
|
+
class XpathParser
|
5
|
+
attr_accessor :xpath
|
6
|
+
|
7
|
+
def initialize(xpath)
|
8
|
+
raise ArgumentError, "XPath shoudn't start with /" if xpath.start_with?('/')
|
9
|
+
@xpath = xpath
|
10
|
+
end
|
11
|
+
|
12
|
+
def nodes
|
13
|
+
@nodes ||= xpath.split('/').map { |element| XpathElement.new(element) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def childrens
|
17
|
+
nodes[1..]
|
18
|
+
end
|
19
|
+
|
20
|
+
def root
|
21
|
+
nodes.first
|
22
|
+
end
|
23
|
+
|
24
|
+
# Each Xpath has one value
|
25
|
+
def value
|
26
|
+
nodes.last.value
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: body_party
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1.pre
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Anas Tammam
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-11-10 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.4'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 2.4.1
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '2.4'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 2.4.1
|
33
|
+
description: It's easier to generate XML or Hash using xpaths as string
|
34
|
+
email: anas.jber@gmail.com
|
35
|
+
executables: []
|
36
|
+
extensions: []
|
37
|
+
extra_rdoc_files: []
|
38
|
+
files:
|
39
|
+
- lib/body_party.rb
|
40
|
+
- lib/document.rb
|
41
|
+
- lib/hash_parser.rb
|
42
|
+
- lib/node.rb
|
43
|
+
- lib/version.rb
|
44
|
+
- lib/xpath_element.rb
|
45
|
+
- lib/xpath_parser.rb
|
46
|
+
homepage: https://github.com/anastammam/body_party
|
47
|
+
licenses:
|
48
|
+
- MIT
|
49
|
+
metadata: {}
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options: []
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '3.0'
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
requirements: []
|
65
|
+
rubygems_version: 3.5.9
|
66
|
+
signing_key:
|
67
|
+
specification_version: 4
|
68
|
+
summary: Convert XPath-like notation into structured XML or Hash formats
|
69
|
+
test_files: []
|