dsv7-parser 7.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.
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dsv7
4
+ class Validator
5
+ class SchemaBase
6
+ def initialize(result)
7
+ @result = result
8
+ end
9
+
10
+ def validate_element(name, attrs, line_number)
11
+ schema = self.class::SCHEMAS[name]
12
+ return unless schema
13
+
14
+ check_count(name, attrs, schema.length, line_number)
15
+ validate_attribute_types(name, attrs, schema, line_number)
16
+ validate_cross_rules(name, attrs, line_number) if respond_to?(:validate_cross_rules, true)
17
+ end
18
+
19
+ private
20
+
21
+ def validate_attribute_types(name, attrs, schema, line_number)
22
+ schema.each_with_index do |spec, i|
23
+ type, required, opts = spec
24
+ val = attrs[i]
25
+
26
+ if (val.nil? || val.empty?) && required
27
+ add_error("Element #{name}: missing required attribute #{i + 1} (line #{line_number})")
28
+ next
29
+ end
30
+ next if val.nil? || val.empty?
31
+
32
+ send("check_#{type}", name, i + 1, val, line_number, opts)
33
+ end
34
+ end
35
+
36
+ def add_error(msg)
37
+ @result.add_error(msg)
38
+ end
39
+
40
+ def check_count(name, attrs, expected, line_number)
41
+ got = attrs.length
42
+ return if got == expected
43
+
44
+ add_error(
45
+ "Element #{name}: expected #{expected} attributes, got #{got} (line #{line_number})"
46
+ )
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../types'
4
+ require_relative 'base'
5
+
6
+ module Dsv7
7
+ class Validator
8
+ # Validates Wettkampfergebnisliste attribute counts and datatypes
9
+ class ErgSchema < SchemaBase
10
+ include WkTypeChecks
11
+
12
+ SCHEMAS = {
13
+ 'ERZEUGER' => [[:zk, true], [:zk, true], [:zk, true]],
14
+ 'VERANSTALTUNG' => [[:zk, true], [:zk, true], [:bahnl, true], [:zeitmessung, true]],
15
+ 'VERANSTALTER' => [[:zk, true]],
16
+ 'AUSRICHTER' => [
17
+ [:zk, true], [:zk, true], [:zk, false], [:zk, false], [:zk, false],
18
+ [:land, false], [:zk, false], [:zk, false], [:zk, true]
19
+ ],
20
+ 'ABSCHNITT' => [[:zahl, true], [:datum, true], [:uhrzeit, true], [:relativ, false]],
21
+ 'KAMPFGERICHT' => [[:zahl, true], [:zk, true], [:zk, true], [:zk, true]],
22
+ 'WETTKAMPF' => [
23
+ [:zahl, true], [:wk_art_erg, true], [:zahl, true], [:zahl, false],
24
+ [:einzelstrecke, true], [:technik, true], [:ausuebung, true],
25
+ [:geschlecht_wk, true], [:bestenliste, true], [:zahl, false], [:wk_art, false]
26
+ ],
27
+ 'WERTUNG' => [
28
+ [:zahl, true], [:wk_art, true], [:zahl, true], [:wert_typ, true],
29
+ [:jgak, true], [:jgak, false], [:geschlecht_erw, false], [:zk, true]
30
+ ],
31
+ 'VEREIN' => [[:zk, true], [:zahl, true], [:zahl, true], [:land, true]],
32
+ 'PNERGEBNIS' => [
33
+ [:zahl, true], [:wk_art_erg, true], [:zahl, true], [:zahl, true],
34
+ [:zk, false], [:zk, true], [:zahl, true], [:zahl, true],
35
+ [:geschlecht_pf, true], [:zahl, true], [:zahl, false], [:zk, true],
36
+ [:zahl, true], [:zeit, true], [:zk, false], [:nachtrag_flag, false],
37
+ [:land, false], [:land, false], [:land, false]
38
+ ],
39
+ 'PNZWISCHENZEIT' => [
40
+ [:zahl, true], [:zahl, true], [:wk_art_erg, true], [:zahl, true], [:zeit, true]
41
+ ],
42
+ 'PNREAKTION' => [
43
+ [:zahl, true], [:zahl, true], [:wk_art_erg, true], [:reaktion_art, false], [:zeit, true]
44
+ ],
45
+ # Accept both names as they appear across sources
46
+ 'STAFFELERGEBNIS' => [
47
+ [:zahl, true], [:wk_art_erg, true], [:zahl, true], [:zahl, true],
48
+ [:zk, false], [:zahl, true], [:zahl, true], [:zk, true], [:zahl, true],
49
+ [:zeit, true], [:zahl, false], [:zk, false], [:nachtrag_flag, false]
50
+ ],
51
+ 'STERGEBNIS' => [
52
+ [:zahl, true], [:wk_art_erg, true], [:zahl, true], [:zahl, true],
53
+ [:zk, false], [:zahl, true], [:zahl, true], [:zk, true], [:zahl, true],
54
+ [:zeit, true], [:zahl, false], [:zk, false], [:nachtrag_flag, false]
55
+ ],
56
+ 'STAFFELPERSON' => [
57
+ [:zahl, true], [:zahl, true], [:wk_art_erg, true], [:zk, true],
58
+ [:zahl, true], [:zahl, true], [:geschlecht_pf, true], [:zahl, true],
59
+ [:zahl, false], [:land, false], [:land, false], [:land, false]
60
+ ],
61
+ 'STZWISCHENZEIT' => [
62
+ [:zahl, true], [:zahl, true], [:wk_art_erg, true], [:zahl, true], [:zahl, true],
63
+ [:zeit, true]
64
+ ],
65
+ 'STABLOESE' => [
66
+ [:zahl, true], [:zahl, true], [:wk_art_erg, true], [:zahl, true],
67
+ [:reaktion_art, false], [:zeit, true]
68
+ ]
69
+ }.freeze
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../types'
4
+ require_relative 'base'
5
+
6
+ module Dsv7
7
+ class Validator
8
+ # Validates Vereinsmeldeliste attribute counts and datatypes
9
+ class VmlSchema < SchemaBase
10
+ include WkTypeChecks
11
+
12
+ SCHEMAS = {
13
+ 'ERZEUGER' => [[:zk, true], [:zk, true], [:zk, true]],
14
+ 'VERANSTALTUNG' => [[:zk, true], [:zk, true], [:bahnl, true], [:zeitmessung, true]],
15
+ 'ABSCHNITT' => [[:zahl, true], [:datum, true], [:uhrzeit, true], [:relativ, false]],
16
+ 'WETTKAMPF' => [
17
+ [:zahl, true], [:wk_art, true], [:zahl, true], [:zahl, false],
18
+ [:einzelstrecke, true], [:technik, true], [:ausuebung, true],
19
+ [:geschlecht_wk, true], [:zahl, false], [:wk_art, false]
20
+ ],
21
+ 'VEREIN' => [[:zk, true], [:zahl, true], [:zahl, true], [:land, true]],
22
+ 'ANSPRECHPARTNER' => [
23
+ [:zk, true], [:zk, false], [:zk, false], [:zk, false],
24
+ [:land, false], [:zk, false], [:zk, false], [:zk, true]
25
+ ],
26
+ 'KARIMELDUNG' => [[:zahl, true], [:zk, true], [:zk, true]],
27
+ 'KARIABSCHNITT' => [[:zahl, true], [:zahl, true], [:zk, false]],
28
+ 'TRAINER' => [[:zahl, true], [:zk, true]],
29
+ 'PNMELDUNG' => [
30
+ [:zk, true], [:zahl, true], [:zahl, true], [:geschlecht_pf, true],
31
+ [:zahl, true], [:zahl, false], [:zahl, false],
32
+ [:land, false], [:land, false], [:land, false]
33
+ ],
34
+ 'HANDICAP' => [
35
+ [:zahl, true], [:zk, false], [:zk, false],
36
+ [:zk, true], [:zk, true], [:zk, true], [:zk, false]
37
+ ],
38
+ 'STARTPN' => [[:zahl, true], [:zahl, true], [:zeit, false]],
39
+ 'STMELDUNG' => [
40
+ [:zahl, true], [:zahl, true], [:wert_typ, true],
41
+ [:jgak, true], [:jgak, false], [:zk, false]
42
+ ],
43
+ 'STARTST' => [[:zahl, true], [:zahl, true], [:zeit, false]],
44
+ 'STAFFELPERSON' => [[:zahl, true], [:zahl, true], [:zahl, true], [:zahl, true]]
45
+ }.freeze
46
+
47
+ # no extra cross-rules for VML currently
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../types'
4
+ require_relative 'base'
5
+
6
+ module Dsv7
7
+ class Validator
8
+ # Validates Vereinsergebnisliste attribute counts and datatypes
9
+ class VrlSchema < SchemaBase
10
+ include WkTypeChecks
11
+
12
+ SCHEMAS = {
13
+ 'ERZEUGER' => [[:zk, true], [:zk, true], [:zk, true]],
14
+ 'VERANSTALTUNG' => [[:zk, true], [:zk, true], [:bahnl, true], [:zeitmessung, true]],
15
+ 'VERANSTALTER' => [[:zk, true]],
16
+ 'AUSRICHTER' => [
17
+ [:zk, true], [:zk, true], [:zk, false], [:zk, false], [:zk, false],
18
+ [:land, false], [:zk, false], [:zk, false], [:zk, true]
19
+ ],
20
+ 'ABSCHNITT' => [[:zahl, true], [:datum, true], [:uhrzeit, true], [:relativ, false]],
21
+ 'KAMPFGERICHT' => [[:zahl, true], [:zk, true], [:zk, true], [:zk, true]],
22
+ 'WETTKAMPF' => [
23
+ [:zahl, true], [:wk_art_erg, true], [:zahl, true], [:zahl, false],
24
+ [:einzelstrecke, true], [:technik, true], [:ausuebung, true],
25
+ [:geschlecht_wk, true], [:bestenliste, true], [:zahl, false], [:wk_art, false]
26
+ ],
27
+ 'WERTUNG' => [
28
+ [:zahl, true], [:wk_art, true], [:zahl, true], [:wert_typ, true],
29
+ [:jgak, true], [:jgak, false], [:geschlecht_erw, false], [:zk, true]
30
+ ],
31
+ 'VEREIN' => [[:zk, true], [:zahl, true], [:zahl, true], [:land, true]],
32
+ 'PERSON' => [
33
+ [:zk, true], [:zahl, true], [:zahl, true], [:geschlecht_pf, true],
34
+ [:zahl, true], [:zahl, false], [:land, false], [:land, false], [:land, false]
35
+ ],
36
+ 'PERSONENERGEBNIS' => [
37
+ [:zahl, true], [:zahl, true], [:wk_art_erg, true], [:zahl, true],
38
+ [:zahl, true], [:zeit, true], [:nichtwertung_grund, false],
39
+ [:zk, false], [:nachtrag_flag, false]
40
+ ],
41
+ 'PNZWISCHENZEIT' => [
42
+ [:zahl, true], [:zahl, true], [:wk_art_erg, true], [:zahl, true], [:zeit, true]
43
+ ],
44
+ 'PNREAKTION' => [
45
+ [:zahl, true], [:zahl, true], [:wk_art_erg, true], [:reaktion_art, false], [:zeit, true]
46
+ ],
47
+ 'STAFFEL' => [
48
+ [:zahl, true], [:zahl, true], [:wert_typ, true],
49
+ [:jgak, true], [:jgak, false]
50
+ ],
51
+ 'STAFFELPERSON' => [
52
+ [:zahl, true], [:zahl, true], [:wk_art_erg, true], [:zk, true],
53
+ [:zahl, true], [:zahl, true], [:geschlecht_pf, true], [:zahl, true],
54
+ [:zahl, false], [:land, false], [:land, false], [:land, false]
55
+ ],
56
+ 'STAFFELERGEBNIS' => [
57
+ [:zahl, true], [:zahl, true], [:wk_art_erg, true], [:zahl, true],
58
+ [:zahl, true], [:zeit, true], [:zk, false], [:zahl, false],
59
+ [:zk, false], [:nachtrag_flag, false]
60
+ ],
61
+ # Accept both names as they appear across sources
62
+ 'STERGEBNIS' => [
63
+ [:zahl, true], [:zahl, true], [:wk_art_erg, true], [:zahl, true],
64
+ [:zahl, true], [:zeit, true], [:zk, false], [:zahl, false],
65
+ [:zk, false], [:nachtrag_flag, false]
66
+ ],
67
+ 'STZWISCHENZEIT' => [
68
+ [:zahl, true], [:zahl, true], [:wk_art_erg, true], [:zahl, true], [:zahl, true],
69
+ [:zeit, true]
70
+ ],
71
+ 'STABLOESE' => [
72
+ [:zahl, true], [:zahl, true], [:wk_art_erg, true], [:zahl, true],
73
+ [:reaktion_art, false], [:zeit, true]
74
+ ]
75
+ }.freeze
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../types'
4
+ require_relative 'base'
5
+
6
+ module Dsv7
7
+ class Validator
8
+ # Validates Wettkampfdefinitionsliste attribute counts and datatypes
9
+ class WkSchema < SchemaBase
10
+ include WkTypeChecks
11
+
12
+ SCHEMAS = {
13
+ 'ERZEUGER' => [[:zk, true], [:zk, true], [:zk, true]],
14
+ 'VERANSTALTUNG' => [
15
+ [:zk, true], [:zk, true], [:bahnl, true], [:zeitmessung, true]
16
+ ],
17
+ 'VERANSTALTUNGSORT' => [
18
+ [:zk, true], [:zk, false], [:zk, false], [:zk, true],
19
+ [:land, true], [:zk, false], [:zk, false], [:zk, false]
20
+ ],
21
+ 'AUSSCHREIBUNGIMNETZ' => [[:zk, false]],
22
+ 'VERANSTALTER' => [[:zk, true]],
23
+ 'AUSRICHTER' => [
24
+ [:zk, true], [:zk, true], [:zk, false], [:zk, false], [:zk, false],
25
+ [:land, false], [:zk, false], [:zk, false], [:zk, true]
26
+ ],
27
+ 'MELDEADRESSE' => [
28
+ [:zk, true], [:zk, false], [:zk, false], [:zk, false],
29
+ [:land, false], [:zk, false], [:zk, false], [:zk, true]
30
+ ],
31
+ 'MELDESCHLUSS' => [[:datum, true], [:uhrzeit, true]],
32
+ 'BANKVERBINDUNG' => [[:zk, false], [:zk, true], [:zk, false]],
33
+ 'BESONDERES' => [[:zk, true]],
34
+ 'NACHWEIS' => [[:datum, true], [:datum, false], [:nachweis_bahn, true]],
35
+ 'ABSCHNITT' => [
36
+ [:zahl, true], [:datum, true], [:uhrzeit, false],
37
+ [:uhrzeit, false], [:uhrzeit, true], [:relativ, false]
38
+ ],
39
+ 'WETTKAMPF' => [
40
+ [:zahl, true], [:wk_art, true], [:zahl, true], [:zahl, false],
41
+ [:einzelstrecke, true], [:technik, true], [:ausuebung, true],
42
+ [:geschlecht_wk, true], [:bestenliste, true], [:zahl, false], [:wk_art, false]
43
+ ],
44
+ 'WERTUNG' => [
45
+ [:zahl, true], [:wk_art, true], [:zahl, true], [:wert_typ, true],
46
+ [:jgak, true], [:jgak, false], [:geschlecht_erw, false], [:zk, true]
47
+ ],
48
+ # Intentionally omitting PFLICHTZEIT for now (spec examples appear inconsistent)
49
+ 'MELDEGELD' => [[:meldegeld_typ, true], [:betrag, true], [:zahl, false]]
50
+ }.freeze
51
+
52
+ def validate_cross_rules(name, attrs, line_number)
53
+ return unless name == 'MELDEGELD'
54
+
55
+ type_str = attrs[0].to_s.upcase
56
+ needs_wk = type_str == 'WKMELDEGELD' && (attrs[2].nil? || attrs[2].empty?)
57
+ return unless needs_wk
58
+
59
+ add_error(
60
+ "Element MELDEGELD: 'WKMELDEGELD' requires Wettkampfnr (attr 3) (line #{line_number})"
61
+ )
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dsv7
4
+ class Validator
5
+ module WkTypeChecksCommon
6
+ def check_zk(_name, _index, _val, _line_number, _opts = nil)
7
+ # any string (already UTF-8 scrubbed); nothing to do
8
+ end
9
+
10
+ def check_zahl(name, idx, val, line_number, _opts = nil)
11
+ return if val.match?(/^\d+$/)
12
+
13
+ add_error(invalid_zahl_error(name, idx, val, line_number))
14
+ end
15
+
16
+ def check_betrag(name, idx, val, line_number, _opts = nil)
17
+ return if val.match?(/^\d+,\d{2}$/)
18
+
19
+ add_error(
20
+ "Element #{name}, attribute #{idx}: invalid Betrag '#{val}' (expected x,yy) " \
21
+ "(line #{line_number})"
22
+ )
23
+ end
24
+
25
+ def check_einzelstrecke(name, idx, val, line_number, _opts = nil)
26
+ return add_error(invalid_zahl_error(name, idx, val, line_number)) unless val.match?(/^\d+$/)
27
+
28
+ n = val.to_i
29
+ return if n.zero? || (1..25_000).cover?(n)
30
+
31
+ add_error(
32
+ "Element #{name}, attribute #{idx}: Einzelstrecke out of range '#{val}' " \
33
+ "(allowed 1..25000 or 0) (line #{line_number})"
34
+ )
35
+ end
36
+
37
+ def invalid_zahl_error(name, idx, val, line_number)
38
+ "Element #{name}, attribute #{idx}: invalid Zahl '#{val}' (line #{line_number})"
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+
5
+ module Dsv7
6
+ class Validator
7
+ module WkTypeChecksDateTime
8
+ def check_datum(name, idx, val, line_number, _opts = nil)
9
+ return add_error(datum_format_error(name, idx, val, line_number)) unless
10
+ val.match?(/^\d{2}\.\d{2}\.\d{4}$/)
11
+
12
+ Date.strptime(val, '%d.%m.%Y')
13
+ rescue ArgumentError
14
+ add_error(impossible_date_error(name, idx, val, line_number))
15
+ end
16
+
17
+ def check_uhrzeit(name, idx, val, line_number, _opts = nil)
18
+ return add_error(uhrzeit_format_error(name, idx, val, line_number)) unless
19
+ val.match?(/^\d{2}:\d{2}$/)
20
+
21
+ hh, mm = val.split(':').map(&:to_i)
22
+ return if (0..23).cover?(hh) && (0..59).cover?(mm)
23
+
24
+ add_error(time_out_of_range_error(name, idx, val, line_number))
25
+ end
26
+
27
+ def check_zeit(name, idx, val, line_number, _opts = nil)
28
+ return add_error(zeit_format_error(name, idx, val, line_number)) unless
29
+ val.match?(/^\d{2}:\d{2}:\d{2},\d{2}$/)
30
+
31
+ h, m, s, hh = parse_zeit_parts(val)
32
+ return if (0..23).cover?(h) && (0..59).cover?(m) && (0..59).cover?(s) && (0..99).cover?(hh)
33
+
34
+ add_error(time_out_of_range_error(name, idx, val, line_number))
35
+ end
36
+
37
+ def datum_format_error(name, idx, val, line_number)
38
+ "Element #{name}, attribute #{idx}: invalid Datum '#{val}' " \
39
+ "(expected TT.MM.JJJJ) (line #{line_number})"
40
+ end
41
+
42
+ def impossible_date_error(name, idx, val, line_number)
43
+ "Element #{name}, attribute #{idx}: impossible date '#{val}' (line #{line_number})"
44
+ end
45
+
46
+ def uhrzeit_format_error(name, idx, val, line_number)
47
+ "Element #{name}, attribute #{idx}: invalid Uhrzeit '#{val}' " \
48
+ "(expected HH:MM) (line #{line_number})"
49
+ end
50
+
51
+ def zeit_format_error(name, idx, val, line_number)
52
+ "Element #{name}, attribute #{idx}: invalid Zeit '#{val}' " \
53
+ "(expected HH:MM:SS,hh) (line #{line_number})"
54
+ end
55
+
56
+ def time_out_of_range_error(name, idx, val, line_number)
57
+ "Element #{name}, attribute #{idx}: time out of range '#{val}' (line #{line_number})"
58
+ end
59
+
60
+ def parse_zeit_parts(val)
61
+ h, m, s_hh = val.split(':')
62
+ s, hh = s_hh.split(',')
63
+ [h.to_i, m.to_i, s.to_i, hh.to_i]
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dsv7
4
+ class Validator
5
+ module WkTypeChecksEnums1
6
+ def check_bahnl(name, idx, val, line_number, _opts = nil)
7
+ allowed = %w[16 20 25 33 50 FW X]
8
+ return if allowed.include?(val)
9
+
10
+ add_error(
11
+ "Element #{name}, attribute #{idx}: invalid Bahnlänge '#{val}' (allowed: " \
12
+ "#{allowed.join(', ')}) (line #{line_number})"
13
+ )
14
+ end
15
+
16
+ def check_zeitmessung(name, idx, val, line_number, _opts = nil)
17
+ allowed = %w[HANDZEIT AUTOMATISCH HALBAUTOMATISCH]
18
+ return if allowed.include?(val)
19
+
20
+ add_error(
21
+ "Element #{name}, attribute #{idx}: invalid Zeitmessung '#{val}' (allowed: " \
22
+ "#{allowed.join(', ')}) (line #{line_number})"
23
+ )
24
+ end
25
+
26
+ def check_land(name, idx, val, line_number, _opts = nil)
27
+ return if val.match?(/^[A-Z]{3}$/)
28
+
29
+ add_error(
30
+ "Element #{name}, attribute #{idx}: invalid Land '#{val}' " \
31
+ "(expected FINA code, e.g., GER) (line #{line_number})"
32
+ )
33
+ end
34
+
35
+ def check_nachweis_bahn(name, idx, val, line_number, _opts = nil)
36
+ allowed = %w[25 50 FW AL]
37
+ return if allowed.include?(val)
38
+
39
+ add_error(
40
+ "Element #{name}, attribute #{idx}: invalid Bahnlänge '#{val}' (allowed: " \
41
+ "#{allowed.join(', ')}) (line #{line_number})"
42
+ )
43
+ end
44
+
45
+ def check_relativ(name, idx, val, line_number, _opts = nil)
46
+ return if %w[J N].include?(val)
47
+
48
+ add_error(
49
+ "Element #{name}, attribute #{idx}: invalid Relative Angabe '#{val}' (allowed: J, N) " \
50
+ "(line #{line_number})"
51
+ )
52
+ end
53
+
54
+ def check_wk_art(name, idx, val, line_number, _opts = nil)
55
+ return if %w[V Z F E].include?(val)
56
+
57
+ add_error(
58
+ "Element #{name}, attribute #{idx}: invalid Wettkampfart '#{val}' " \
59
+ "(allowed: V, Z, F, E) (line #{line_number})"
60
+ )
61
+ end
62
+
63
+ # Wettkampfergebnisliste allows additional values 'A' and 'N'
64
+ def check_wk_art_erg(name, idx, val, line_number, _opts = nil)
65
+ return if %w[V Z F E A N].include?(val)
66
+
67
+ add_error(
68
+ "Element #{name}, attribute #{idx}: invalid Wettkampfart '#{val}' " \
69
+ "(allowed: V, Z, F, E, A, N) (line #{line_number})"
70
+ )
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dsv7
4
+ class Validator
5
+ module WkTypeChecksEnums2
6
+ def check_technik(name, idx, val, line_number, _opts = nil)
7
+ return if %w[F R B S L X].include?(val)
8
+
9
+ add_error(
10
+ "Element #{name}, attribute #{idx}: invalid Technik '#{val}' " \
11
+ "(allowed: F, R, B, S, L, X) (line #{line_number})"
12
+ )
13
+ end
14
+
15
+ def check_ausuebung(name, idx, val, line_no, _opts = nil)
16
+ return if %w[GL BE AR ST WE GB X].include?(val)
17
+
18
+ add_error(
19
+ "Element #{name}, attribute #{idx}: invalid Ausübung '#{val}' " \
20
+ "(allowed: GL, BE, AR, ST, WE, GB, X) (line #{line_no})"
21
+ )
22
+ end
23
+
24
+ def check_geschlecht_wk(name, idx, val, line_no, _opts = nil)
25
+ return if %w[M W X].include?(val)
26
+
27
+ add_error(
28
+ "Element #{name}, attribute #{idx}: invalid Geschlecht '#{val}' (allowed: M, W, X) " \
29
+ "(line #{line_no})"
30
+ )
31
+ end
32
+
33
+ def check_bestenliste(name, idx, val, line_no, _opts = nil)
34
+ return if %w[SW EW PA MS KG XX].include?(val)
35
+
36
+ add_error(
37
+ "Element #{name}, attribute #{idx}: invalid Zuordnung '#{val}' " \
38
+ "(allowed: SW, EW, PA, MS, KG, XX) (line #{line_no})"
39
+ )
40
+ end
41
+
42
+ def check_wert_typ(name, idx, val, line_number, _opts = nil)
43
+ return if %w[JG AK].include?(val)
44
+
45
+ add_error(
46
+ "Element #{name}, attribute #{idx}: invalid Wertungstyp '#{val}' (allowed: JG, AK) " \
47
+ "(line #{line_number})"
48
+ )
49
+ end
50
+
51
+ def check_jgak(name, idx, val, line_number, _opts = nil)
52
+ return if val.match?(/^\d{1,4}$/) || val.match?(/^[ABCDEJ]$/) || val.match?(/^\d{2,3}\+$/)
53
+
54
+ add_error(
55
+ "Element #{name}, attribute #{idx}: invalid JG/AK '#{val}' (line #{line_number})"
56
+ )
57
+ end
58
+
59
+ def check_geschlecht_erw(name, idx, val, line_number, _opts = nil)
60
+ return if %w[M W X D].include?(val)
61
+
62
+ add_error(
63
+ "Element #{name}, attribute #{idx}: invalid Geschlecht '#{val}' (allowed: M, W, X, D) " \
64
+ "(line #{line_number})"
65
+ )
66
+ end
67
+
68
+ def check_geschlecht_pf(name, idx, val, line_number, _opts = nil)
69
+ return if %w[M W D].include?(val)
70
+
71
+ add_error(
72
+ "Element #{name}, attribute #{idx}: invalid Geschlecht '#{val}' (allowed: M, W, D) " \
73
+ "(line #{line_number})"
74
+ )
75
+ end
76
+
77
+ def check_meldegeld_typ(name, idx, val, line_number, _opts = nil)
78
+ allowed = %w[
79
+ MELDEGELDPAUSCHALE EINZELMELDEGELD STAFFELMELDEGELD WKMELDEGELD MANNSCHAFTMELDEGELD
80
+ ]
81
+ return if allowed.include?(val.upcase)
82
+
83
+ add_error(
84
+ "Element #{name}, attribute #{idx}: invalid Meldegeld Typ '#{val}' (line #{line_number})"
85
+ )
86
+ end
87
+
88
+ def check_reaktion_art(name, idx, val, line_number, _opts = nil)
89
+ return if %w[+ -].include?(val)
90
+
91
+ add_error(
92
+ "Element #{name}, attribute #{idx}: invalid Reaktionsart '#{val}' (allowed: +, -) " \
93
+ "(line #{line_number})"
94
+ )
95
+ end
96
+
97
+ def check_nachtrag_flag(name, idx, val, line_number, _opts = nil)
98
+ return if %w[E F N].include?(val)
99
+
100
+ add_error(
101
+ "Element #{name}, attribute #{idx}: invalid Nachtragskennzeichen '#{val}' " \
102
+ "(allowed: E, F, N) (line #{line_number})"
103
+ )
104
+ end
105
+
106
+ def check_nichtwertung_grund(name, idx, val, line_number, _opts = nil)
107
+ allowed = %w[DS NA AB AU ZU]
108
+ return if allowed.include?(val)
109
+
110
+ add_error(
111
+ "Element #{name}, attribute #{idx}: invalid Grund der Nichtwertung '#{val}' " \
112
+ "(allowed: #{allowed.join(', ')}) (line #{line_number})"
113
+ )
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'types/common'
4
+ require_relative 'types/datetime'
5
+ require_relative 'types/enums1'
6
+ require_relative 'types/enums2'
7
+
8
+ module Dsv7
9
+ class Validator
10
+ module WkTypeChecks
11
+ include WkTypeChecksCommon
12
+ include WkTypeChecksDateTime
13
+ include WkTypeChecksEnums1
14
+ include WkTypeChecksEnums2
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'stringio'
4
+ require_relative 'validator/result'
5
+ require_relative 'validator/core'
6
+
7
+ module Dsv7
8
+ # Validates overall conformity of a DSV7 file against high-level rules
9
+ # extracted from the specification markdown under `specification/dsv7`.
10
+ #
11
+ # Scope: structural and format-level checks that do not require
12
+ # knowledge of the full element schemas.
13
+ class Validator
14
+ # Known list types from the overview section
15
+ ALLOWED_LIST_TYPES = %w[
16
+ Wettkampfdefinitionsliste
17
+ Vereinsmeldeliste
18
+ Wettkampfergebnisliste
19
+ Vereinsergebnisliste
20
+ ].freeze
21
+
22
+ # Single public entrypoint. Accepts:
23
+ # - IO-like objects (respond_to?(:read)) → streamed
24
+ # - String paths to files → streamed
25
+ # - String content → streamed via StringIO
26
+ def self.validate(input)
27
+ return new.send(:validate_stream, input) if input.respond_to?(:read)
28
+ return validate_path(input) if input.is_a?(String) && File.file?(input)
29
+ return new.send(:validate_stream, StringIO.new(input.b)) if input.is_a?(String)
30
+
31
+ raise ArgumentError, 'Unsupported input; pass IO, file path String, or content String'
32
+ end
33
+
34
+ class << self
35
+ private
36
+
37
+ def validate_path(path)
38
+ File.open(path, 'rb') do |io|
39
+ return new.send(:validate_stream, io, filename: File.basename(path))
40
+ end
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def validate_stream(io, filename: nil)
47
+ result = Result.new
48
+ Core.new(result, filename).call_io(io)
49
+ end
50
+ end
51
+ end