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.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.rubocop.yml +22 -0
- data/.ruby-version +1 -0
- data/AGENTS.md +255 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +61 -0
- data/LICENSE +21 -0
- data/README.md +370 -0
- data/Rakefile +24 -0
- data/TODO.md +20 -0
- data/dsv7-parser.gemspec +33 -0
- data/lib/dsv7/lex.rb +27 -0
- data/lib/dsv7/parser/engine.rb +116 -0
- data/lib/dsv7/parser/io_util.rb +42 -0
- data/lib/dsv7/parser/version.rb +7 -0
- data/lib/dsv7/parser.rb +66 -0
- data/lib/dsv7/stream.rb +61 -0
- data/lib/dsv7/validator/cardinality.rb +168 -0
- data/lib/dsv7/validator/core.rb +57 -0
- data/lib/dsv7/validator/line_analyzer.rb +141 -0
- data/lib/dsv7/validator/line_analyzer_common.rb +140 -0
- data/lib/dsv7/validator/result.rb +34 -0
- data/lib/dsv7/validator/schemas/base.rb +50 -0
- data/lib/dsv7/validator/schemas/erg_schema.rb +72 -0
- data/lib/dsv7/validator/schemas/vml_schema.rb +50 -0
- data/lib/dsv7/validator/schemas/vrl_schema.rb +78 -0
- data/lib/dsv7/validator/schemas/wk_schema.rb +65 -0
- data/lib/dsv7/validator/types/common.rb +42 -0
- data/lib/dsv7/validator/types/datetime.rb +67 -0
- data/lib/dsv7/validator/types/enums1.rb +74 -0
- data/lib/dsv7/validator/types/enums2.rb +117 -0
- data/lib/dsv7/validator/types.rb +17 -0
- data/lib/dsv7/validator.rb +51 -0
- data/specification/dsv7/dsv7_specification.md +1305 -0
- metadata +78 -0
|
@@ -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
|