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 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
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ox'
4
+
5
+ require 'xpath_parser'
6
+ require 'xpath_element'
7
+ require 'document'
8
+ require 'node'
9
+ require 'hash_parser'
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
@@ -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
@@ -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: []