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.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +21 -0
  3. data/.yardopts +2 -0
  4. data/LICENSE +21 -0
  5. data/README.md +796 -0
  6. data/Rakefile +43 -0
  7. data/bin/console +8 -0
  8. data/bin/setup +5 -0
  9. data/lenex-parser.gemspec +35 -0
  10. data/lib/lenex/document/serializer.rb +191 -0
  11. data/lib/lenex/document.rb +163 -0
  12. data/lib/lenex/parser/objects/age_date.rb +53 -0
  13. data/lib/lenex/parser/objects/age_group.rb +86 -0
  14. data/lib/lenex/parser/objects/athlete.rb +93 -0
  15. data/lib/lenex/parser/objects/bank.rb +56 -0
  16. data/lib/lenex/parser/objects/club.rb +101 -0
  17. data/lib/lenex/parser/objects/constructor.rb +51 -0
  18. data/lib/lenex/parser/objects/contact.rb +55 -0
  19. data/lib/lenex/parser/objects/entry.rb +70 -0
  20. data/lib/lenex/parser/objects/entry_schedule.rb +40 -0
  21. data/lib/lenex/parser/objects/event.rb +114 -0
  22. data/lib/lenex/parser/objects/facility.rb +58 -0
  23. data/lib/lenex/parser/objects/fee.rb +54 -0
  24. data/lib/lenex/parser/objects/fee_schedule.rb +26 -0
  25. data/lib/lenex/parser/objects/handicap.rb +86 -0
  26. data/lib/lenex/parser/objects/heat.rb +58 -0
  27. data/lib/lenex/parser/objects/host_club.rb +34 -0
  28. data/lib/lenex/parser/objects/judge.rb +55 -0
  29. data/lib/lenex/parser/objects/lenex.rb +72 -0
  30. data/lib/lenex/parser/objects/meet.rb +175 -0
  31. data/lib/lenex/parser/objects/meet_info.rb +60 -0
  32. data/lib/lenex/parser/objects/official.rb +70 -0
  33. data/lib/lenex/parser/objects/organizer.rb +34 -0
  34. data/lib/lenex/parser/objects/point_table.rb +54 -0
  35. data/lib/lenex/parser/objects/pool.rb +44 -0
  36. data/lib/lenex/parser/objects/qualify.rb +55 -0
  37. data/lib/lenex/parser/objects/ranking.rb +54 -0
  38. data/lib/lenex/parser/objects/record.rb +107 -0
  39. data/lib/lenex/parser/objects/record_athlete.rb +92 -0
  40. data/lib/lenex/parser/objects/record_list.rb +106 -0
  41. data/lib/lenex/parser/objects/record_relay.rb +62 -0
  42. data/lib/lenex/parser/objects/record_relay_position.rb +62 -0
  43. data/lib/lenex/parser/objects/relay.rb +93 -0
  44. data/lib/lenex/parser/objects/relay_entry.rb +81 -0
  45. data/lib/lenex/parser/objects/relay_position.rb +74 -0
  46. data/lib/lenex/parser/objects/relay_result.rb +85 -0
  47. data/lib/lenex/parser/objects/result.rb +76 -0
  48. data/lib/lenex/parser/objects/session.rb +107 -0
  49. data/lib/lenex/parser/objects/split.rb +53 -0
  50. data/lib/lenex/parser/objects/swim_style.rb +58 -0
  51. data/lib/lenex/parser/objects/time_standard.rb +55 -0
  52. data/lib/lenex/parser/objects/time_standard_list.rb +98 -0
  53. data/lib/lenex/parser/objects/time_standard_ref.rb +63 -0
  54. data/lib/lenex/parser/objects.rb +52 -0
  55. data/lib/lenex/parser/sax/document_handler.rb +184 -0
  56. data/lib/lenex/parser/version.rb +8 -0
  57. data/lib/lenex/parser/zip_source.rb +111 -0
  58. data/lib/lenex/parser.rb +184 -0
  59. data/lib/lenex-parser.rb +16 -0
  60. metadata +132 -0
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lenex
4
+ module Parser
5
+ module Objects
6
+ # Value object representing a RELAY element.
7
+ class Relay
8
+ ATTRIBUTES = {
9
+ 'agemax' => { key: :age_max, required: true },
10
+ 'agemin' => { key: :age_min, required: true },
11
+ 'agetotalmax' => { key: :age_total_max, required: true },
12
+ 'agetotalmin' => { key: :age_total_min, required: true },
13
+ 'gender' => { key: :gender, required: true },
14
+ 'handicap' => { key: :handicap, required: false },
15
+ 'name' => { key: :name, required: false },
16
+ 'number' => { key: :number, required: false }
17
+ }.freeze
18
+
19
+ ATTRIBUTE_KEYS = ATTRIBUTES.values.map { |definition| definition[:key] }.freeze
20
+ private_constant :ATTRIBUTE_KEYS
21
+
22
+ ATTRIBUTE_KEYS.each { |attribute| attr_reader attribute }
23
+ attr_reader :relay_positions, :entries, :results
24
+
25
+ def initialize(relay_positions: [], entries: [], results: [], **attributes)
26
+ ATTRIBUTES.each_value do |definition|
27
+ key = definition[:key]
28
+ instance_variable_set(:"@#{key}", attributes[key])
29
+ end
30
+ @relay_positions = Array(relay_positions)
31
+ @entries = Array(entries)
32
+ @results = Array(results)
33
+ end
34
+
35
+ def self.from_xml(element)
36
+ raise ::Lenex::Parser::ParseError, 'RELAY element is required' unless element
37
+
38
+ attributes = extract_attributes(element)
39
+ relay_positions = extract_relay_positions(element.at_xpath('RELAYPOSITIONS'))
40
+ entries = extract_entries(element.at_xpath('ENTRIES'))
41
+ results = extract_results(element.at_xpath('RESULTS'))
42
+
43
+ new(**attributes, relay_positions:, entries:, results:)
44
+ end
45
+
46
+ def self.extract_attributes(element)
47
+ ATTRIBUTES.each_with_object({}) do |(attribute_name, definition), collected|
48
+ value = element.attribute(attribute_name)&.value
49
+ ensure_required_attribute!(attribute_name, definition, value)
50
+ collected[definition[:key]] = value if value
51
+ end
52
+ end
53
+ private_class_method :extract_attributes
54
+
55
+ def self.ensure_required_attribute!(attribute_name, definition, value)
56
+ return unless definition[:required]
57
+ return unless value.nil? || value.strip.empty?
58
+
59
+ message = "RELAY #{attribute_name} attribute is required"
60
+ raise ::Lenex::Parser::ParseError, message
61
+ end
62
+ private_class_method :ensure_required_attribute!
63
+
64
+ def self.extract_relay_positions(collection_element)
65
+ return [] unless collection_element
66
+
67
+ collection_element.xpath('RELAYPOSITION').map do |position_element|
68
+ RelayPosition.from_xml(position_element)
69
+ end
70
+ end
71
+ private_class_method :extract_relay_positions
72
+
73
+ def self.extract_entries(collection_element)
74
+ return [] unless collection_element
75
+
76
+ collection_element.xpath('ENTRY').map do |entry_element|
77
+ RelayEntry.from_xml(entry_element)
78
+ end
79
+ end
80
+ private_class_method :extract_entries
81
+
82
+ def self.extract_results(collection_element)
83
+ return [] unless collection_element
84
+
85
+ collection_element.xpath('RESULT').map do |result_element|
86
+ RelayResult.from_xml(result_element)
87
+ end
88
+ end
89
+ private_class_method :extract_results
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lenex
4
+ module Parser
5
+ module Objects
6
+ # Value object representing a relay ENTRY element.
7
+ class RelayEntry
8
+ ATTRIBUTES = {
9
+ 'agegroupid' => { key: :age_group_id, required: false },
10
+ 'entrycourse' => { key: :entry_course, required: false },
11
+ 'entrydistance' => { key: :entry_distance, required: false },
12
+ 'entrytime' => { key: :entry_time, required: false },
13
+ 'eventid' => { key: :event_id, required: true },
14
+ 'handicap' => { key: :handicap, required: false },
15
+ 'heatid' => { key: :heat_id, required: false },
16
+ 'lane' => { key: :lane, required: false },
17
+ 'status' => { key: :status, 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 :meet_info, :relay_positions
25
+
26
+ def initialize(meet_info: nil, relay_positions: [], **attributes)
27
+ ATTRIBUTES.each_value do |definition|
28
+ key = definition[:key]
29
+ instance_variable_set(:"@#{key}", attributes[key])
30
+ end
31
+ @meet_info = meet_info
32
+ @relay_positions = Array(relay_positions)
33
+ end
34
+
35
+ def self.from_xml(element)
36
+ raise ::Lenex::Parser::ParseError, 'ENTRY element is required' unless element
37
+
38
+ attributes = extract_attributes(element)
39
+ meet_info = meet_info_from(element.at_xpath('MEETINFO'))
40
+ relay_positions = extract_relay_positions(element.at_xpath('RELAYPOSITIONS'))
41
+
42
+ new(**attributes, meet_info:, relay_positions:)
43
+ end
44
+
45
+ def self.extract_attributes(element)
46
+ ATTRIBUTES.each_with_object({}) do |(attribute_name, definition), collected|
47
+ value = element.attribute(attribute_name)&.value
48
+ ensure_required_attribute!(attribute_name, definition, value)
49
+ collected[definition[:key]] = value if value
50
+ end
51
+ end
52
+ private_class_method :extract_attributes
53
+
54
+ def self.ensure_required_attribute!(attribute_name, definition, value)
55
+ return unless definition[:required]
56
+ return unless value.nil? || value.strip.empty?
57
+
58
+ message = "ENTRY #{attribute_name} attribute is required"
59
+ raise ::Lenex::Parser::ParseError, message
60
+ end
61
+ private_class_method :ensure_required_attribute!
62
+
63
+ def self.meet_info_from(element)
64
+ return unless element
65
+
66
+ MeetInfo.from_xml(element)
67
+ end
68
+ private_class_method :meet_info_from
69
+
70
+ def self.extract_relay_positions(collection_element)
71
+ return [] unless collection_element
72
+
73
+ collection_element.xpath('RELAYPOSITION').map do |position_element|
74
+ RelayPosition.from_xml(position_element)
75
+ end
76
+ end
77
+ private_class_method :extract_relay_positions
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lenex
4
+ module Parser
5
+ module Objects
6
+ # Value object representing a RELAYPOSITION element.
7
+ class RelayPosition
8
+ ATTRIBUTES = {
9
+ 'athleteid' => { key: :athlete_id, required: false },
10
+ 'number' => { key: :number, required: true },
11
+ 'reactiontime' => { key: :reaction_time, required: false },
12
+ 'status' => { key: :status, 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
+ attr_reader :athlete, :meet_info
20
+
21
+ def initialize(athlete: nil, meet_info: nil, **attributes)
22
+ ATTRIBUTES.each_value do |definition|
23
+ key = definition[:key]
24
+ instance_variable_set(:"@#{key}", attributes[key])
25
+ end
26
+ @athlete = athlete
27
+ @meet_info = meet_info
28
+ end
29
+
30
+ def self.from_xml(element)
31
+ raise ::Lenex::Parser::ParseError, 'RELAYPOSITION element is required' unless element
32
+
33
+ attributes = extract_attributes(element)
34
+ athlete = athlete_from(element.at_xpath('ATHLETE'))
35
+ meet_info = meet_info_from(element.at_xpath('MEETINFO'))
36
+
37
+ new(**attributes, athlete:, meet_info:)
38
+ end
39
+
40
+ def self.extract_attributes(element)
41
+ ATTRIBUTES.each_with_object({}) do |(attribute_name, definition), collected|
42
+ value = element.attribute(attribute_name)&.value
43
+ ensure_required_attribute!(attribute_name, definition, value)
44
+ collected[definition[:key]] = value if value
45
+ end
46
+ end
47
+ private_class_method :extract_attributes
48
+
49
+ def self.ensure_required_attribute!(attribute_name, definition, value)
50
+ return unless definition[:required]
51
+ return unless value.nil? || value.strip.empty?
52
+
53
+ message = "RELAYPOSITION #{attribute_name} attribute is required"
54
+ raise ::Lenex::Parser::ParseError, message
55
+ end
56
+ private_class_method :ensure_required_attribute!
57
+
58
+ def self.athlete_from(element)
59
+ return unless element
60
+
61
+ Athlete.from_xml(element)
62
+ end
63
+ private_class_method :athlete_from
64
+
65
+ def self.meet_info_from(element)
66
+ return unless element
67
+
68
+ MeetInfo.from_xml(element)
69
+ end
70
+ private_class_method :meet_info_from
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lenex
4
+ module Parser
5
+ module Objects
6
+ # Value object representing a relay RESULT element.
7
+ class RelayResult
8
+ ATTRIBUTES = {
9
+ 'comment' => { key: :comment, required: false },
10
+ 'eventid' => { key: :event_id, required: false },
11
+ 'handicap' => { key: :handicap, required: false },
12
+ 'heatid' => { key: :heat_id, required: false },
13
+ 'lane' => { key: :lane, required: false },
14
+ 'points' => { key: :points, required: false },
15
+ 'reactiontime' => { key: :reaction_time, required: false },
16
+ 'resultid' => { key: :result_id, required: true },
17
+ 'status' => { key: :status, required: false },
18
+ 'swimdistance' => { key: :swim_distance, required: false },
19
+ 'swimtime' => { key: :swim_time, required: true }
20
+ }.freeze
21
+
22
+ ATTRIBUTE_KEYS = ATTRIBUTES.values.map { |definition| definition[:key] }.freeze
23
+ private_constant :ATTRIBUTE_KEYS
24
+
25
+ ATTRIBUTE_KEYS.each { |attribute| attr_reader attribute }
26
+ attr_reader :relay_positions, :splits
27
+
28
+ def initialize(relay_positions: [], splits: [], **attributes)
29
+ ATTRIBUTES.each_value do |definition|
30
+ key = definition[:key]
31
+ instance_variable_set(:"@#{key}", attributes[key])
32
+ end
33
+ @relay_positions = Array(relay_positions)
34
+ @splits = Array(splits)
35
+ end
36
+
37
+ def self.from_xml(element)
38
+ raise ::Lenex::Parser::ParseError, 'RESULT element is required' unless element
39
+
40
+ attributes = extract_attributes(element)
41
+ relay_positions = extract_relay_positions(element.at_xpath('RELAYPOSITIONS'))
42
+ splits = extract_splits(element.at_xpath('SPLITS'))
43
+
44
+ new(**attributes, relay_positions:, splits:)
45
+ end
46
+
47
+ def self.extract_attributes(element)
48
+ ATTRIBUTES.each_with_object({}) do |(attribute_name, definition), collected|
49
+ value = element.attribute(attribute_name)&.value
50
+ ensure_required_attribute!(attribute_name, definition, value)
51
+ collected[definition[:key]] = value if value
52
+ end
53
+ end
54
+ private_class_method :extract_attributes
55
+
56
+ def self.ensure_required_attribute!(attribute_name, definition, value)
57
+ return unless definition[:required]
58
+ return unless value.nil? || value.strip.empty?
59
+
60
+ message = "RESULT #{attribute_name} attribute is required"
61
+ raise ::Lenex::Parser::ParseError, message
62
+ end
63
+ private_class_method :ensure_required_attribute!
64
+
65
+ def self.extract_relay_positions(collection_element)
66
+ return [] unless collection_element
67
+
68
+ collection_element.xpath('RELAYPOSITION').map do |position_element|
69
+ RelayPosition.from_xml(position_element)
70
+ end
71
+ end
72
+ private_class_method :extract_relay_positions
73
+
74
+ def self.extract_splits(collection_element)
75
+ return [] unless collection_element
76
+
77
+ collection_element.xpath('SPLIT').map do |split_element|
78
+ Split.from_xml(split_element)
79
+ end
80
+ end
81
+ private_class_method :extract_splits
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lenex
4
+ module Parser
5
+ module Objects
6
+ # Value object representing a RESULT element.
7
+ class Result
8
+ ATTRIBUTES = {
9
+ 'comment' => { key: :comment, required: false },
10
+ 'entrycourse' => { key: :entry_course, required: false },
11
+ 'entrytime' => { key: :entry_time, required: false },
12
+ 'eventid' => { key: :event_id, required: false },
13
+ 'handicap' => { key: :handicap, required: false },
14
+ 'heatid' => { key: :heat_id, required: false },
15
+ 'lane' => { key: :lane, required: false },
16
+ 'points' => { key: :points, required: false },
17
+ 'reactiontime' => { key: :reaction_time, required: false },
18
+ 'resultid' => { key: :result_id, required: true },
19
+ 'status' => { key: :status, required: false },
20
+ 'swimdistance' => { key: :swim_distance, required: false },
21
+ 'swimtime' => { key: :swim_time, required: true }
22
+ }.freeze
23
+
24
+ ATTRIBUTE_KEYS = ATTRIBUTES.values.map { |definition| definition[:key] }.freeze
25
+ private_constant :ATTRIBUTE_KEYS
26
+
27
+ ATTRIBUTE_KEYS.each { |attribute| attr_reader attribute }
28
+ attr_reader :splits
29
+
30
+ def initialize(splits: [], **attributes)
31
+ ATTRIBUTES.each_value do |definition|
32
+ key = definition[:key]
33
+ instance_variable_set(:"@#{key}", attributes[key])
34
+ end
35
+ @splits = Array(splits)
36
+ end
37
+
38
+ def self.from_xml(element)
39
+ raise ::Lenex::Parser::ParseError, 'RESULT element is required' unless element
40
+
41
+ attributes = extract_attributes(element)
42
+ splits = extract_splits(element.at_xpath('SPLITS'))
43
+
44
+ new(**attributes, splits:)
45
+ end
46
+
47
+ def self.extract_attributes(element)
48
+ ATTRIBUTES.each_with_object({}) do |(attribute_name, definition), collected|
49
+ value = element.attribute(attribute_name)&.value
50
+ ensure_required_attribute!(attribute_name, definition, value)
51
+ collected[definition[:key]] = value if value
52
+ end
53
+ end
54
+ private_class_method :extract_attributes
55
+
56
+ def self.ensure_required_attribute!(attribute_name, definition, value)
57
+ return unless definition[:required]
58
+ return unless value.nil? || value.strip.empty?
59
+
60
+ message = "RESULT #{attribute_name} attribute is required"
61
+ raise ::Lenex::Parser::ParseError, message
62
+ end
63
+ private_class_method :ensure_required_attribute!
64
+
65
+ def self.extract_splits(collection_element)
66
+ return [] unless collection_element
67
+
68
+ collection_element.xpath('SPLIT').map do |split_element|
69
+ Split.from_xml(split_element)
70
+ end
71
+ end
72
+ private_class_method :extract_splits
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lenex
4
+ module Parser
5
+ module Objects
6
+ # Value object representing a SESSION element.
7
+ class Session
8
+ ATTRIBUTES = {
9
+ 'course' => { key: :course, required: false },
10
+ 'date' => { key: :date, required: true },
11
+ 'daytime' => { key: :daytime, required: false },
12
+ 'endtime' => { key: :endtime, required: false },
13
+ 'maxentriesathlete' => { key: :max_entries_athlete, required: false },
14
+ 'maxentriesrelay' => { key: :max_entries_relay, required: false },
15
+ 'name' => { key: :name, required: false },
16
+ 'number' => { key: :number, required: true },
17
+ 'officialmeeting' => { key: :official_meeting, required: false },
18
+ 'remarksjudge' => { key: :remarks_judge, required: false },
19
+ 'teamleadermeeting' => { key: :team_leader_meeting, required: false },
20
+ 'timing' => { key: :timing, required: false },
21
+ 'touchpadmode' => { key: :touchpad_mode, required: false },
22
+ 'warmupfrom' => { key: :warmup_from, required: false },
23
+ 'warmupuntil' => { key: :warmup_until, required: false }
24
+ }.freeze
25
+
26
+ ATTRIBUTE_KEYS = ATTRIBUTES.values.map { |definition| definition[:key] }.freeze
27
+ private_constant :ATTRIBUTE_KEYS
28
+
29
+ ATTRIBUTE_KEYS.each { |attribute| attr_reader attribute }
30
+ attr_reader :fee_schedule, :pool, :judges, :events
31
+
32
+ def initialize(fee_schedule: nil, pool: nil, judges: [], events: [], **attributes)
33
+ ATTRIBUTES.each_value do |definition|
34
+ key = definition[:key]
35
+ instance_variable_set(:"@#{key}", attributes[key])
36
+ end
37
+ @fee_schedule = fee_schedule
38
+ @pool = pool
39
+ @judges = Array(judges)
40
+ @events = Array(events)
41
+ end
42
+
43
+ def self.from_xml(element)
44
+ raise ::Lenex::Parser::ParseError, 'SESSION element is required' unless element
45
+
46
+ attributes = extract_attributes(element)
47
+
48
+ fee_schedule = FeeSchedule.from_xml(element.at_xpath('FEES'))
49
+ pool = pool_from(element.at_xpath('POOL'))
50
+ judges = extract_judges(element.at_xpath('JUDGES'))
51
+ events = extract_events(element.at_xpath('EVENTS'))
52
+
53
+ new(**attributes, fee_schedule:, pool:, judges:, events:)
54
+ end
55
+
56
+ def self.extract_attributes(element)
57
+ ATTRIBUTES.each_with_object({}) do |(attribute_name, definition), collected|
58
+ value = element.attribute(attribute_name)&.value
59
+ ensure_required_attribute!(attribute_name, definition, value)
60
+ collected[definition[:key]] = value if value
61
+ end
62
+ end
63
+ private_class_method :extract_attributes
64
+
65
+ def self.ensure_required_attribute!(attribute_name, definition, value)
66
+ return unless definition[:required]
67
+ return unless value.nil? || value.strip.empty?
68
+
69
+ message = "SESSION #{attribute_name} attribute is required"
70
+ raise ::Lenex::Parser::ParseError, message
71
+ end
72
+ private_class_method :ensure_required_attribute!
73
+
74
+ def self.pool_from(element)
75
+ return unless element
76
+
77
+ Pool.from_xml(element)
78
+ end
79
+ private_class_method :pool_from
80
+
81
+ def self.extract_judges(collection_element)
82
+ return [] unless collection_element
83
+
84
+ collection_element.xpath('JUDGE').map do |judge_element|
85
+ Judge.from_xml(judge_element)
86
+ end
87
+ end
88
+ private_class_method :extract_judges
89
+
90
+ def self.extract_events(collection_element)
91
+ unless collection_element
92
+ raise ::Lenex::Parser::ParseError, 'SESSION EVENTS element is required'
93
+ end
94
+
95
+ events = collection_element.xpath('EVENT').map do |event_element|
96
+ Event.from_xml(event_element)
97
+ end
98
+
99
+ return events unless events.empty?
100
+
101
+ raise ::Lenex::Parser::ParseError, 'SESSION must include at least one EVENT element'
102
+ end
103
+ private_class_method :extract_events
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lenex
4
+ module Parser
5
+ module Objects
6
+ # Value object representing a SPLIT element.
7
+ class Split
8
+ ATTRIBUTES = {
9
+ 'distance' => { key: :distance, required: true },
10
+ 'swimtime' => { key: :swim_time, required: true }
11
+ }.freeze
12
+
13
+ ATTRIBUTE_KEYS = ATTRIBUTES.values.map { |definition| definition[:key] }.freeze
14
+ private_constant :ATTRIBUTE_KEYS
15
+
16
+ ATTRIBUTE_KEYS.each { |attribute| attr_reader attribute }
17
+
18
+ def initialize(**attributes)
19
+ ATTRIBUTES.each_value do |definition|
20
+ key = definition[:key]
21
+ instance_variable_set(:"@#{key}", attributes[key])
22
+ end
23
+ end
24
+
25
+ def self.from_xml(element)
26
+ raise ::Lenex::Parser::ParseError, 'SPLIT element is required' unless element
27
+
28
+ attributes = extract_attributes(element)
29
+
30
+ new(**attributes)
31
+ end
32
+
33
+ def self.extract_attributes(element)
34
+ ATTRIBUTES.each_with_object({}) do |(attribute_name, definition), collected|
35
+ value = element.attribute(attribute_name)&.value
36
+ ensure_required_attribute!(attribute_name, definition, value)
37
+ collected[definition[:key]] = value if value
38
+ end
39
+ end
40
+ private_class_method :extract_attributes
41
+
42
+ def self.ensure_required_attribute!(attribute_name, definition, value)
43
+ return unless definition[:required]
44
+ return unless value.nil? || value.strip.empty?
45
+
46
+ message = "SPLIT #{attribute_name} attribute is required"
47
+ raise ::Lenex::Parser::ParseError, message
48
+ end
49
+ private_class_method :ensure_required_attribute!
50
+ end
51
+ end
52
+ end
53
+ 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 SWIMSTYLE element.
7
+ class SwimStyle
8
+ ATTRIBUTES = {
9
+ 'code' => { key: :code, required: false },
10
+ 'distance' => { key: :distance, required: true },
11
+ 'name' => { key: :name, required: false },
12
+ 'relaycount' => { key: :relay_count, required: true },
13
+ 'stroke' => { key: :stroke, required: true },
14
+ 'swimstyleid' => { key: :swim_style_id, required: false },
15
+ 'technique' => { key: :technique, 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, 'SWIMSTYLE 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 = "SWIMSTYLE #{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,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lenex
4
+ module Parser
5
+ module Objects
6
+ # Value object representing a TIMESTANDARD element.
7
+ class TimeStandard
8
+ ATTRIBUTES = {
9
+ 'swimtime' => { key: :swim_time, required: true }
10
+ }.freeze
11
+
12
+ ATTRIBUTE_KEYS = ATTRIBUTES.values.map { |definition| definition[:key] }.freeze
13
+ private_constant :ATTRIBUTE_KEYS
14
+
15
+ ATTRIBUTE_KEYS.each { |attribute| attr_reader attribute }
16
+ attr_reader :swim_style
17
+
18
+ def initialize(swim_style:, **attributes)
19
+ ATTRIBUTES.each_value do |definition|
20
+ key = definition[:key]
21
+ instance_variable_set(:"@#{key}", attributes[key])
22
+ end
23
+ @swim_style = swim_style
24
+ end
25
+
26
+ def self.from_xml(element)
27
+ raise ::Lenex::Parser::ParseError, 'TIMESTANDARD element is required' unless element
28
+
29
+ attributes = extract_attributes(element)
30
+ swim_style = SwimStyle.from_xml(element.at_xpath('SWIMSTYLE'))
31
+
32
+ new(**attributes, swim_style:)
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 = "TIMESTANDARD #{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