lenex-parser 3.0.0
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/.rubocop.yml +21 -0
- data/.yardopts +2 -0
- data/LICENSE +21 -0
- data/README.md +796 -0
- data/Rakefile +43 -0
- data/bin/console +8 -0
- data/bin/setup +5 -0
- data/lenex-parser.gemspec +35 -0
- data/lib/lenex/document/serializer.rb +191 -0
- data/lib/lenex/document.rb +163 -0
- data/lib/lenex/parser/objects/age_date.rb +53 -0
- data/lib/lenex/parser/objects/age_group.rb +86 -0
- data/lib/lenex/parser/objects/athlete.rb +93 -0
- data/lib/lenex/parser/objects/bank.rb +56 -0
- data/lib/lenex/parser/objects/club.rb +101 -0
- data/lib/lenex/parser/objects/constructor.rb +51 -0
- data/lib/lenex/parser/objects/contact.rb +55 -0
- data/lib/lenex/parser/objects/entry.rb +70 -0
- data/lib/lenex/parser/objects/entry_schedule.rb +40 -0
- data/lib/lenex/parser/objects/event.rb +114 -0
- data/lib/lenex/parser/objects/facility.rb +58 -0
- data/lib/lenex/parser/objects/fee.rb +54 -0
- data/lib/lenex/parser/objects/fee_schedule.rb +26 -0
- data/lib/lenex/parser/objects/handicap.rb +86 -0
- data/lib/lenex/parser/objects/heat.rb +58 -0
- data/lib/lenex/parser/objects/host_club.rb +34 -0
- data/lib/lenex/parser/objects/judge.rb +55 -0
- data/lib/lenex/parser/objects/lenex.rb +72 -0
- data/lib/lenex/parser/objects/meet.rb +175 -0
- data/lib/lenex/parser/objects/meet_info.rb +60 -0
- data/lib/lenex/parser/objects/official.rb +70 -0
- data/lib/lenex/parser/objects/organizer.rb +34 -0
- data/lib/lenex/parser/objects/point_table.rb +54 -0
- data/lib/lenex/parser/objects/pool.rb +44 -0
- data/lib/lenex/parser/objects/qualify.rb +55 -0
- data/lib/lenex/parser/objects/ranking.rb +54 -0
- data/lib/lenex/parser/objects/record.rb +107 -0
- data/lib/lenex/parser/objects/record_athlete.rb +92 -0
- data/lib/lenex/parser/objects/record_list.rb +106 -0
- data/lib/lenex/parser/objects/record_relay.rb +62 -0
- data/lib/lenex/parser/objects/record_relay_position.rb +62 -0
- data/lib/lenex/parser/objects/relay.rb +93 -0
- data/lib/lenex/parser/objects/relay_entry.rb +81 -0
- data/lib/lenex/parser/objects/relay_position.rb +74 -0
- data/lib/lenex/parser/objects/relay_result.rb +85 -0
- data/lib/lenex/parser/objects/result.rb +76 -0
- data/lib/lenex/parser/objects/session.rb +107 -0
- data/lib/lenex/parser/objects/split.rb +53 -0
- data/lib/lenex/parser/objects/swim_style.rb +58 -0
- data/lib/lenex/parser/objects/time_standard.rb +55 -0
- data/lib/lenex/parser/objects/time_standard_list.rb +98 -0
- data/lib/lenex/parser/objects/time_standard_ref.rb +63 -0
- data/lib/lenex/parser/objects.rb +52 -0
- data/lib/lenex/parser/sax/document_handler.rb +184 -0
- data/lib/lenex/parser/version.rb +8 -0
- data/lib/lenex/parser/zip_source.rb +111 -0
- data/lib/lenex/parser.rb +184 -0
- data/lib/lenex-parser.rb +16 -0
- metadata +132 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lenex
|
|
4
|
+
module Parser
|
|
5
|
+
module Objects
|
|
6
|
+
# Value object representing a HANDICAP element.
|
|
7
|
+
class Handicap
|
|
8
|
+
ATTRIBUTES = {
|
|
9
|
+
'breast' => { key: :breast, required: true, missing_behavior: :warn },
|
|
10
|
+
'breaststatus' => { key: :breast_status, required: false },
|
|
11
|
+
'exception' => { key: :exception, required: false },
|
|
12
|
+
'free' => { key: :free, required: false },
|
|
13
|
+
'freestatus' => { key: :free_status, required: false },
|
|
14
|
+
'medley' => { key: :medley, required: false },
|
|
15
|
+
'medleystatus' => { key: :medley_status, required: false }
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
ATTRIBUTE_KEYS = ATTRIBUTES.values.map { |definition| definition[:key] }.freeze
|
|
19
|
+
private_constant :ATTRIBUTE_KEYS
|
|
20
|
+
|
|
21
|
+
ATTRIBUTE_KEYS.each { |attribute| attr_reader attribute }
|
|
22
|
+
|
|
23
|
+
def initialize(**attributes)
|
|
24
|
+
ATTRIBUTES.each_value do |definition|
|
|
25
|
+
key = definition[:key]
|
|
26
|
+
instance_variable_set(:"@#{key}", attributes[key])
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.from_xml(element)
|
|
31
|
+
return unless element
|
|
32
|
+
|
|
33
|
+
attributes = extract_attributes(element)
|
|
34
|
+
ensure_required_attributes!(attributes)
|
|
35
|
+
|
|
36
|
+
new(**attributes)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.extract_attributes(element)
|
|
40
|
+
ATTRIBUTES.each_with_object({}) do |(attribute_name, definition), collected|
|
|
41
|
+
value = element.attribute(attribute_name)&.value
|
|
42
|
+
collected[definition[:key]] = value if value && !value.strip.empty?
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
private_class_method :extract_attributes
|
|
46
|
+
|
|
47
|
+
def self.ensure_required_attributes!(attributes)
|
|
48
|
+
ATTRIBUTES.each_value do |definition|
|
|
49
|
+
next unless definition[:required]
|
|
50
|
+
|
|
51
|
+
key = definition[:key]
|
|
52
|
+
value = attributes[key]
|
|
53
|
+
next unless value.nil? || value.strip.empty?
|
|
54
|
+
|
|
55
|
+
attribute_name = ATTRIBUTE_NAME_FOR.fetch(key)
|
|
56
|
+
message = "HANDICAP #{attribute_name} attribute is required"
|
|
57
|
+
handle_missing_required_attribute(definition, message)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
private_class_method :ensure_required_attributes!
|
|
61
|
+
|
|
62
|
+
def self.handle_missing_required_attribute(definition, message)
|
|
63
|
+
behavior = definition.fetch(:missing_behavior, :raise)
|
|
64
|
+
case behavior
|
|
65
|
+
when :warn
|
|
66
|
+
emit_warning(message)
|
|
67
|
+
else
|
|
68
|
+
raise ::Lenex::Parser::ParseError, message
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
private_class_method :handle_missing_required_attribute
|
|
72
|
+
|
|
73
|
+
def self.emit_warning(message)
|
|
74
|
+
warn(message)
|
|
75
|
+
end
|
|
76
|
+
private_class_method :emit_warning
|
|
77
|
+
|
|
78
|
+
ATTRIBUTE_NAME_FOR = ATTRIBUTES.each_with_object({}) do |(attribute_name, definition),
|
|
79
|
+
mapping|
|
|
80
|
+
mapping[definition[:key]] = attribute_name
|
|
81
|
+
end.freeze
|
|
82
|
+
private_constant :ATTRIBUTE_NAME_FOR
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lenex
|
|
4
|
+
module Parser
|
|
5
|
+
module Objects
|
|
6
|
+
# Value object representing a HEAT element.
|
|
7
|
+
class Heat
|
|
8
|
+
ATTRIBUTES = {
|
|
9
|
+
'agegroupid' => { key: :age_group_id, required: false },
|
|
10
|
+
'daytime' => { key: :daytime, required: false },
|
|
11
|
+
'final' => { key: :final, required: false },
|
|
12
|
+
'heatid' => { key: :heat_id, required: true },
|
|
13
|
+
'number' => { key: :number, required: true },
|
|
14
|
+
'order' => { key: :order, required: false },
|
|
15
|
+
'status' => { key: :status, required: false }
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
ATTRIBUTE_KEYS = ATTRIBUTES.values.map { |definition| definition[:key] }.freeze
|
|
19
|
+
private_constant :ATTRIBUTE_KEYS
|
|
20
|
+
|
|
21
|
+
ATTRIBUTE_KEYS.each { |attribute| attr_reader attribute }
|
|
22
|
+
|
|
23
|
+
def initialize(**attributes)
|
|
24
|
+
ATTRIBUTES.each_value do |definition|
|
|
25
|
+
key = definition[:key]
|
|
26
|
+
instance_variable_set(:"@#{key}", attributes[key])
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.from_xml(element)
|
|
31
|
+
raise ::Lenex::Parser::ParseError, 'HEAT element is required' unless element
|
|
32
|
+
|
|
33
|
+
attributes = extract_attributes(element)
|
|
34
|
+
|
|
35
|
+
new(**attributes)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.extract_attributes(element)
|
|
39
|
+
ATTRIBUTES.each_with_object({}) do |(attribute_name, definition), collected|
|
|
40
|
+
value = element.attribute(attribute_name)&.value
|
|
41
|
+
ensure_required_attribute!(attribute_name, definition, value)
|
|
42
|
+
collected[definition[:key]] = value if value
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
private_class_method :extract_attributes
|
|
46
|
+
|
|
47
|
+
def self.ensure_required_attribute!(attribute_name, definition, value)
|
|
48
|
+
return unless definition[:required]
|
|
49
|
+
return unless value.nil? || value.strip.empty?
|
|
50
|
+
|
|
51
|
+
message = "HEAT #{attribute_name} attribute is required"
|
|
52
|
+
raise ::Lenex::Parser::ParseError, message
|
|
53
|
+
end
|
|
54
|
+
private_class_method :ensure_required_attribute!
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lenex
|
|
4
|
+
module Parser
|
|
5
|
+
module Objects
|
|
6
|
+
# Value object representing host club metadata on a MEET element.
|
|
7
|
+
class HostClub
|
|
8
|
+
attr_reader :name, :url
|
|
9
|
+
|
|
10
|
+
def initialize(name:, url: nil)
|
|
11
|
+
@name = name
|
|
12
|
+
@url = url
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.from_xml(meet_element)
|
|
16
|
+
name = attribute_value(meet_element, 'hostclub')
|
|
17
|
+
url = attribute_value(meet_element, 'hostclub.url')
|
|
18
|
+
|
|
19
|
+
return unless name || url
|
|
20
|
+
|
|
21
|
+
new(name:, url:)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.attribute_value(element, attribute_name)
|
|
25
|
+
value = element.attribute(attribute_name)&.value
|
|
26
|
+
return if value.nil? || value.strip.empty?
|
|
27
|
+
|
|
28
|
+
value
|
|
29
|
+
end
|
|
30
|
+
private_class_method :attribute_value
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lenex
|
|
4
|
+
module Parser
|
|
5
|
+
module Objects
|
|
6
|
+
# Value object representing a JUDGE element.
|
|
7
|
+
class Judge
|
|
8
|
+
ATTRIBUTES = {
|
|
9
|
+
'number' => { key: :number, required: false },
|
|
10
|
+
'officialid' => { key: :official_id, required: true },
|
|
11
|
+
'remarks' => { key: :remarks, required: false },
|
|
12
|
+
'role' => { key: :role, required: false }
|
|
13
|
+
}.freeze
|
|
14
|
+
|
|
15
|
+
ATTRIBUTE_KEYS = ATTRIBUTES.values.map { |definition| definition[:key] }.freeze
|
|
16
|
+
private_constant :ATTRIBUTE_KEYS
|
|
17
|
+
|
|
18
|
+
ATTRIBUTE_KEYS.each { |attribute| attr_reader attribute }
|
|
19
|
+
|
|
20
|
+
def initialize(**attributes)
|
|
21
|
+
ATTRIBUTES.each_value do |definition|
|
|
22
|
+
key = definition[:key]
|
|
23
|
+
instance_variable_set(:"@#{key}", attributes[key])
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.from_xml(element)
|
|
28
|
+
raise ::Lenex::Parser::ParseError, 'JUDGE element is required' unless element
|
|
29
|
+
|
|
30
|
+
attributes = extract_attributes(element)
|
|
31
|
+
|
|
32
|
+
new(**attributes)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.extract_attributes(element)
|
|
36
|
+
ATTRIBUTES.each_with_object({}) do |(attribute_name, definition), collected|
|
|
37
|
+
value = element.attribute(attribute_name)&.value
|
|
38
|
+
ensure_required_attribute!(attribute_name, definition, value)
|
|
39
|
+
collected[definition[:key]] = value if value
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
private_class_method :extract_attributes
|
|
43
|
+
|
|
44
|
+
def self.ensure_required_attribute!(attribute_name, definition, value)
|
|
45
|
+
return unless definition[:required]
|
|
46
|
+
return unless value.nil? || value.strip.empty?
|
|
47
|
+
|
|
48
|
+
message = "JUDGE #{attribute_name} attribute is required"
|
|
49
|
+
raise ::Lenex::Parser::ParseError, message
|
|
50
|
+
end
|
|
51
|
+
private_class_method :ensure_required_attribute!
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lenex
|
|
4
|
+
module Parser
|
|
5
|
+
module Objects
|
|
6
|
+
# Value object representing the LENEX root element.
|
|
7
|
+
class Lenex
|
|
8
|
+
attr_reader :version, :revision, :constructor, :meets, :record_lists, :time_standard_lists
|
|
9
|
+
|
|
10
|
+
def initialize(version:, revision:, constructor:, collections: {})
|
|
11
|
+
@version = version
|
|
12
|
+
@revision = revision
|
|
13
|
+
@constructor = constructor
|
|
14
|
+
@meets = Array(collections.fetch(:meets, []))
|
|
15
|
+
@record_lists = Array(collections.fetch(:record_lists, []))
|
|
16
|
+
@time_standard_lists = Array(collections.fetch(:time_standard_lists, []))
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.from_xml(element)
|
|
20
|
+
version = version_from(element)
|
|
21
|
+
constructor = Constructor.from_xml(element.at_xpath('CONSTRUCTOR'))
|
|
22
|
+
revision = element.attribute('revision')&.value
|
|
23
|
+
collections = build_collections(element)
|
|
24
|
+
|
|
25
|
+
new(version:, revision:, constructor:, collections:)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.version_from(element)
|
|
29
|
+
version = element.attribute('version')&.value
|
|
30
|
+
return version if version && !version.strip.empty?
|
|
31
|
+
|
|
32
|
+
raise ::Lenex::Parser::ParseError, 'LENEX version attribute is required'
|
|
33
|
+
end
|
|
34
|
+
private_class_method :version_from
|
|
35
|
+
|
|
36
|
+
def self.build_collections(element)
|
|
37
|
+
{
|
|
38
|
+
meets: extract_meets(element.at_xpath('MEETS')),
|
|
39
|
+
record_lists: extract_record_lists(element.at_xpath('RECORDLISTS')),
|
|
40
|
+
time_standard_lists: extract_time_standard_lists(element.at_xpath('TIMESTANDARDLISTS'))
|
|
41
|
+
}
|
|
42
|
+
end
|
|
43
|
+
private_class_method :build_collections
|
|
44
|
+
|
|
45
|
+
def self.extract_meets(collection_element)
|
|
46
|
+
return [] unless collection_element
|
|
47
|
+
|
|
48
|
+
collection_element.xpath('MEET').map { |meet_element| Meet.from_xml(meet_element) }
|
|
49
|
+
end
|
|
50
|
+
private_class_method :extract_meets
|
|
51
|
+
|
|
52
|
+
def self.extract_record_lists(collection_element)
|
|
53
|
+
return [] unless collection_element
|
|
54
|
+
|
|
55
|
+
collection_element.xpath('RECORDLIST').map do |record_list_element|
|
|
56
|
+
RecordList.from_xml(record_list_element)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
private_class_method :extract_record_lists
|
|
60
|
+
|
|
61
|
+
def self.extract_time_standard_lists(collection_element)
|
|
62
|
+
return [] unless collection_element
|
|
63
|
+
|
|
64
|
+
collection_element.xpath('TIMESTANDARDLIST').map do |time_standard_list_element|
|
|
65
|
+
TimeStandardList.from_xml(time_standard_list_element)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
private_class_method :extract_time_standard_lists
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lenex
|
|
4
|
+
module Parser
|
|
5
|
+
module Objects
|
|
6
|
+
# Helper namespace that extracts MEET associations from XML nodes.
|
|
7
|
+
module MeetAssociations
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def build(element)
|
|
11
|
+
metadata_from(element).merge(collections_from(element))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def metadata_from(element)
|
|
15
|
+
core_metadata(element).merge(optional_metadata(element))
|
|
16
|
+
end
|
|
17
|
+
private_class_method :metadata_from
|
|
18
|
+
|
|
19
|
+
def core_metadata(element)
|
|
20
|
+
{
|
|
21
|
+
contact: contact_from(element.at_xpath('CONTACT')),
|
|
22
|
+
age_date: association_from(element.at_xpath('AGEDATE'), AgeDate),
|
|
23
|
+
bank: association_from(element.at_xpath('BANK'), Bank),
|
|
24
|
+
facility: association_from(element.at_xpath('FACILITY'), Facility),
|
|
25
|
+
point_table: association_from(element.at_xpath('POINTTABLE'), PointTable),
|
|
26
|
+
qualify: association_from(element.at_xpath('QUALIFY'), Qualify),
|
|
27
|
+
pool: association_from(element.at_xpath('POOL'), Pool)
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
private_class_method :core_metadata
|
|
31
|
+
|
|
32
|
+
def optional_metadata(element)
|
|
33
|
+
{
|
|
34
|
+
fee_schedule: FeeSchedule.from_xml(element.at_xpath('FEES')),
|
|
35
|
+
host_club: HostClub.from_xml(element),
|
|
36
|
+
organizer: Organizer.from_xml(element),
|
|
37
|
+
entry_schedule: EntrySchedule.from_xml(element)
|
|
38
|
+
}
|
|
39
|
+
end
|
|
40
|
+
private_class_method :optional_metadata
|
|
41
|
+
|
|
42
|
+
def collections_from(element)
|
|
43
|
+
{
|
|
44
|
+
clubs: extract_clubs(element.at_xpath('CLUBS')),
|
|
45
|
+
sessions: extract_sessions(element.at_xpath('SESSIONS'))
|
|
46
|
+
}
|
|
47
|
+
end
|
|
48
|
+
private_class_method :collections_from
|
|
49
|
+
|
|
50
|
+
def association_from(element, klass)
|
|
51
|
+
return unless element
|
|
52
|
+
|
|
53
|
+
klass.from_xml(element)
|
|
54
|
+
end
|
|
55
|
+
private_class_method :association_from
|
|
56
|
+
|
|
57
|
+
def contact_from(element)
|
|
58
|
+
return unless element
|
|
59
|
+
|
|
60
|
+
Contact.from_xml(element)
|
|
61
|
+
end
|
|
62
|
+
private_class_method :contact_from
|
|
63
|
+
|
|
64
|
+
def extract_clubs(collection_element)
|
|
65
|
+
return [] unless collection_element
|
|
66
|
+
|
|
67
|
+
collection_element.xpath('CLUB').map { |club_element| Club.from_xml(club_element) }
|
|
68
|
+
end
|
|
69
|
+
private_class_method :extract_clubs
|
|
70
|
+
|
|
71
|
+
def extract_sessions(collection_element)
|
|
72
|
+
unless collection_element
|
|
73
|
+
raise ::Lenex::Parser::ParseError, 'MEET SESSIONS element is required'
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
session_elements = collection_element.xpath('SESSION')
|
|
77
|
+
if session_elements.empty?
|
|
78
|
+
raise ::Lenex::Parser::ParseError, 'MEET SESSIONS element is required'
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
session_elements.map { |session_element| Session.from_xml(session_element) }
|
|
82
|
+
end
|
|
83
|
+
private_class_method :extract_sessions
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Value object representing a MEET element.
|
|
87
|
+
class Meet
|
|
88
|
+
ATTRIBUTES = {
|
|
89
|
+
'name' => { key: :name, required: true },
|
|
90
|
+
'name.en' => { key: :name_en, required: false },
|
|
91
|
+
'city' => { key: :city, required: true },
|
|
92
|
+
'city.en' => { key: :city_en, required: false },
|
|
93
|
+
'nation' => { key: :nation, required: true },
|
|
94
|
+
'course' => { key: :course, required: false },
|
|
95
|
+
'number' => { key: :number, required: false },
|
|
96
|
+
'reservecount' => { key: :reserve_count, required: false },
|
|
97
|
+
'startmethod' => { key: :start_method, required: false },
|
|
98
|
+
'timing' => { key: :timing, required: false },
|
|
99
|
+
'touchpadmode' => { key: :touchpad_mode, required: false },
|
|
100
|
+
'type' => { key: :type, required: false },
|
|
101
|
+
'entrytype' => { key: :entry_type, required: false },
|
|
102
|
+
'maxentriesathlete' => { key: :max_entries_athlete, required: false },
|
|
103
|
+
'maxentriesrelay' => { key: :max_entries_relay, required: false },
|
|
104
|
+
'altitude' => { key: :altitude, required: false },
|
|
105
|
+
'swrid' => { key: :swrid, required: false },
|
|
106
|
+
'result.url' => { key: :result_url, required: false }
|
|
107
|
+
}.freeze
|
|
108
|
+
|
|
109
|
+
ATTRIBUTE_KEYS = ATTRIBUTES.values.map { |definition| definition[:key] }.freeze
|
|
110
|
+
private_constant :ATTRIBUTE_KEYS
|
|
111
|
+
|
|
112
|
+
ASSOCIATION_DEFAULTS = {
|
|
113
|
+
contact: nil,
|
|
114
|
+
clubs: [],
|
|
115
|
+
sessions: [],
|
|
116
|
+
age_date: nil,
|
|
117
|
+
bank: nil,
|
|
118
|
+
facility: nil,
|
|
119
|
+
point_table: nil,
|
|
120
|
+
qualify: nil,
|
|
121
|
+
pool: nil,
|
|
122
|
+
fee_schedule: nil,
|
|
123
|
+
host_club: nil,
|
|
124
|
+
organizer: nil,
|
|
125
|
+
entry_schedule: nil
|
|
126
|
+
}.freeze
|
|
127
|
+
|
|
128
|
+
ASSOCIATION_KEYS = ASSOCIATION_DEFAULTS.keys.freeze
|
|
129
|
+
private_constant :ASSOCIATION_KEYS
|
|
130
|
+
|
|
131
|
+
ATTRIBUTE_KEYS.each { |attribute| attr_reader attribute }
|
|
132
|
+
ASSOCIATION_KEYS.each { |attribute| attr_reader attribute }
|
|
133
|
+
|
|
134
|
+
def initialize(**attributes)
|
|
135
|
+
ATTRIBUTES.each_value do |definition|
|
|
136
|
+
key = definition[:key]
|
|
137
|
+
instance_variable_set(:"@#{key}", attributes[key])
|
|
138
|
+
end
|
|
139
|
+
ASSOCIATION_DEFAULTS.each do |key, default|
|
|
140
|
+
value = attributes.fetch(key, default)
|
|
141
|
+
value = Array(value) if %i[clubs sessions].include?(key)
|
|
142
|
+
instance_variable_set(:"@#{key}", value)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def self.from_xml(element)
|
|
147
|
+
raise ::Lenex::Parser::ParseError, 'MEET element is required' unless element
|
|
148
|
+
|
|
149
|
+
attributes = extract_attributes(element)
|
|
150
|
+
associations = MeetAssociations.build(element)
|
|
151
|
+
|
|
152
|
+
new(**attributes, **associations)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def self.extract_attributes(element)
|
|
156
|
+
ATTRIBUTES.each_with_object({}) do |(attribute_name, definition), collected|
|
|
157
|
+
value = element.attribute(attribute_name)&.value
|
|
158
|
+
ensure_required_attribute!(attribute_name, definition, value)
|
|
159
|
+
collected[definition[:key]] = value if value
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
private_class_method :extract_attributes
|
|
163
|
+
|
|
164
|
+
def self.ensure_required_attribute!(attribute_name, definition, value)
|
|
165
|
+
return unless definition[:required]
|
|
166
|
+
return unless value.nil? || value.strip.empty?
|
|
167
|
+
|
|
168
|
+
message = "MEET #{attribute_name} attribute is required"
|
|
169
|
+
raise ::Lenex::Parser::ParseError, message
|
|
170
|
+
end
|
|
171
|
+
private_class_method :ensure_required_attribute!
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lenex
|
|
4
|
+
module Parser
|
|
5
|
+
module Objects
|
|
6
|
+
# Value object representing a MEETINFO element.
|
|
7
|
+
class MeetInfo
|
|
8
|
+
ATTRIBUTES = {
|
|
9
|
+
'approved' => :approved,
|
|
10
|
+
'city' => :city,
|
|
11
|
+
'course' => :course,
|
|
12
|
+
'date' => :date,
|
|
13
|
+
'daytime' => :daytime,
|
|
14
|
+
'name' => :name,
|
|
15
|
+
'nation' => :nation,
|
|
16
|
+
'qualificationtime' => :qualification_time,
|
|
17
|
+
'state' => :state,
|
|
18
|
+
'timing' => :timing
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
ATTRIBUTE_KEYS = ATTRIBUTES.values.freeze
|
|
22
|
+
private_constant :ATTRIBUTE_KEYS
|
|
23
|
+
|
|
24
|
+
ATTRIBUTE_KEYS.each { |attribute| attr_reader attribute }
|
|
25
|
+
attr_reader :pool
|
|
26
|
+
|
|
27
|
+
def initialize(pool: nil, **attributes)
|
|
28
|
+
ATTRIBUTES.each_value do |key|
|
|
29
|
+
instance_variable_set(:"@#{key}", attributes[key])
|
|
30
|
+
end
|
|
31
|
+
@pool = pool
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.from_xml(element)
|
|
35
|
+
raise ::Lenex::Parser::ParseError, 'MEETINFO element is required' unless element
|
|
36
|
+
|
|
37
|
+
attributes = extract_attributes(element)
|
|
38
|
+
pool = pool_from(element.at_xpath('POOL'))
|
|
39
|
+
|
|
40
|
+
new(**attributes, pool:)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.extract_attributes(element)
|
|
44
|
+
ATTRIBUTES.each_with_object({}) do |(attribute_name, key), collected|
|
|
45
|
+
value = element.attribute(attribute_name)&.value
|
|
46
|
+
collected[key] = value if value
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
private_class_method :extract_attributes
|
|
50
|
+
|
|
51
|
+
def self.pool_from(element)
|
|
52
|
+
return unless element
|
|
53
|
+
|
|
54
|
+
Pool.from_xml(element)
|
|
55
|
+
end
|
|
56
|
+
private_class_method :pool_from
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lenex
|
|
4
|
+
module Parser
|
|
5
|
+
module Objects
|
|
6
|
+
# Value object representing an OFFICIAL element.
|
|
7
|
+
class Official
|
|
8
|
+
ATTRIBUTES = {
|
|
9
|
+
'firstname' => { key: :first_name, required: true },
|
|
10
|
+
'gender' => { key: :gender, required: false },
|
|
11
|
+
'grade' => { key: :grade, required: false },
|
|
12
|
+
'lastname' => { key: :last_name, required: true },
|
|
13
|
+
'license' => { key: :license, required: false },
|
|
14
|
+
'nameprefix' => { key: :name_prefix, required: false },
|
|
15
|
+
'nation' => { key: :nation, required: false },
|
|
16
|
+
'officialid' => { key: :official_id, required: true },
|
|
17
|
+
'passport' => { key: :passport, required: false }
|
|
18
|
+
}.freeze
|
|
19
|
+
|
|
20
|
+
ATTRIBUTE_KEYS = ATTRIBUTES.values.map { |definition| definition[:key] }.freeze
|
|
21
|
+
private_constant :ATTRIBUTE_KEYS
|
|
22
|
+
|
|
23
|
+
ATTRIBUTE_KEYS.each { |attribute| attr_reader attribute }
|
|
24
|
+
attr_reader :contact
|
|
25
|
+
|
|
26
|
+
def initialize(contact: nil, **attributes)
|
|
27
|
+
ATTRIBUTES.each_value do |definition|
|
|
28
|
+
key = definition[:key]
|
|
29
|
+
instance_variable_set(:"@#{key}", attributes[key])
|
|
30
|
+
end
|
|
31
|
+
@contact = contact
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.from_xml(element)
|
|
35
|
+
raise ::Lenex::Parser::ParseError, 'OFFICIAL element is required' unless element
|
|
36
|
+
|
|
37
|
+
attributes = extract_attributes(element)
|
|
38
|
+
contact = contact_from(element.at_xpath('CONTACT'))
|
|
39
|
+
|
|
40
|
+
new(**attributes, contact:)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.extract_attributes(element)
|
|
44
|
+
ATTRIBUTES.each_with_object({}) do |(attribute_name, definition), collected|
|
|
45
|
+
value = element.attribute(attribute_name)&.value
|
|
46
|
+
ensure_required_attribute!(attribute_name, definition, value)
|
|
47
|
+
collected[definition[:key]] = value if value
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
private_class_method :extract_attributes
|
|
51
|
+
|
|
52
|
+
def self.ensure_required_attribute!(attribute_name, definition, value)
|
|
53
|
+
return unless definition[:required]
|
|
54
|
+
return unless value.nil? || value.strip.empty?
|
|
55
|
+
|
|
56
|
+
message = "OFFICIAL #{attribute_name} attribute is required"
|
|
57
|
+
raise ::Lenex::Parser::ParseError, message
|
|
58
|
+
end
|
|
59
|
+
private_class_method :ensure_required_attribute!
|
|
60
|
+
|
|
61
|
+
def self.contact_from(element)
|
|
62
|
+
return unless element
|
|
63
|
+
|
|
64
|
+
Contact.from_xml(element)
|
|
65
|
+
end
|
|
66
|
+
private_class_method :contact_from
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lenex
|
|
4
|
+
module Parser
|
|
5
|
+
module Objects
|
|
6
|
+
# Value object representing organizer metadata on a MEET element.
|
|
7
|
+
class Organizer
|
|
8
|
+
attr_reader :name, :url
|
|
9
|
+
|
|
10
|
+
def initialize(name:, url: nil)
|
|
11
|
+
@name = name
|
|
12
|
+
@url = url
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.from_xml(meet_element)
|
|
16
|
+
name = attribute_value(meet_element, 'organizer')
|
|
17
|
+
url = attribute_value(meet_element, 'organizer.url')
|
|
18
|
+
|
|
19
|
+
return unless name || url
|
|
20
|
+
|
|
21
|
+
new(name:, url:)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.attribute_value(element, attribute_name)
|
|
25
|
+
value = element.attribute(attribute_name)&.value
|
|
26
|
+
return if value.nil? || value.strip.empty?
|
|
27
|
+
|
|
28
|
+
value
|
|
29
|
+
end
|
|
30
|
+
private_class_method :attribute_value
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|