dsv7-parser 7.0.0 → 7.0.2
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 +4 -4
- data/.yardopts +5 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +7 -1
- data/README.md +126 -51
- data/Rakefile +38 -0
- data/dsv7-parser.gemspec +5 -1
- data/lib/dsv7/lex.rb +15 -2
- data/lib/dsv7/parser/engine.rb +10 -0
- data/lib/dsv7/parser/io_util.rb +26 -0
- data/lib/dsv7/parser/version.rb +4 -1
- data/lib/dsv7/parser.rb +110 -8
- data/lib/dsv7/stream.rb +26 -0
- data/lib/dsv7/validator/cardinality.rb +6 -0
- data/lib/dsv7/validator/core.rb +18 -0
- data/lib/dsv7/validator/line_analyzer.rb +21 -5
- data/lib/dsv7/validator/line_analyzer_common.rb +25 -0
- data/lib/dsv7/validator/result.rb +34 -0
- data/lib/dsv7/validator/schemas/base.rb +26 -0
- data/lib/dsv7/validator/schemas/erg_schema.rb +5 -1
- data/lib/dsv7/validator/schemas/vml_schema.rb +3 -1
- data/lib/dsv7/validator/schemas/vrl_schema.rb +5 -1
- data/lib/dsv7/validator/schemas/wk_schema.rb +8 -1
- data/lib/dsv7/validator/types/common.rb +13 -0
- data/lib/dsv7/validator/types/datetime.rb +12 -0
- data/lib/dsv7/validator/types/enums1.rb +10 -0
- data/lib/dsv7/validator/types/enums2.rb +10 -0
- data/lib/dsv7/validator/types.rb +8 -0
- data/lib/dsv7/validator.rb +49 -2
- metadata +7 -3
data/lib/dsv7/parser.rb
CHANGED
|
@@ -7,14 +7,64 @@ require_relative 'stream'
|
|
|
7
7
|
require_relative 'lex'
|
|
8
8
|
|
|
9
9
|
module Dsv7
|
|
10
|
+
##
|
|
11
|
+
# Dsv7::Parser
|
|
12
|
+
#
|
|
13
|
+
# Streaming parser for DSV7 lists. It yields a simple event stream so callers
|
|
14
|
+
# can build their own structures without loading the whole file into memory.
|
|
15
|
+
# The parser is intentionally tolerant (e.g., it scrubs invalid UTF‑8 and
|
|
16
|
+
# accepts BOM) — pair it with {Dsv7::Validator} for strict conformance.
|
|
17
|
+
#
|
|
18
|
+
# Events
|
|
19
|
+
# - `[:format, { list_type: String, version: String }, line_number]` — first
|
|
20
|
+
# effective line must be a FORMAT line.
|
|
21
|
+
# - `[:element, { name: String, attrs: Array<String> }, line_number]` — for
|
|
22
|
+
# each element line between FORMAT and DATEIENDE.
|
|
23
|
+
# - `[:end, nil, line_number]` — emitted after `DATEIENDE` (or EOF if missing).
|
|
24
|
+
#
|
|
25
|
+
# @api public
|
|
26
|
+
# @since 7.0.0
|
|
27
|
+
# @example Enumerate events for any list type
|
|
28
|
+
# Dsv7::Parser.parse(io_or_path_or_string) do |type, payload, ln|
|
|
29
|
+
# case type
|
|
30
|
+
# when :format then # inspect payload[:list_type], payload[:version]
|
|
31
|
+
# when :element then # payload[:name], payload[:attrs]
|
|
32
|
+
# when :end then # done
|
|
33
|
+
# end
|
|
34
|
+
# end
|
|
35
|
+
#
|
|
36
|
+
# Documenting new helpers
|
|
37
|
+
# - Describe when a helper raises (e.g., wrong list type for a type‑specific
|
|
38
|
+
# parser) and what it yields.
|
|
39
|
+
# - Note streaming/encoding behavior and that comments are stripped inline.
|
|
10
40
|
module Parser
|
|
41
|
+
# Error type raised by the parser when the input
|
|
42
|
+
# does not satisfy basic envelope expectations.
|
|
11
43
|
class Error < StandardError; end
|
|
12
44
|
|
|
45
|
+
# @!group Parsers
|
|
46
|
+
#
|
|
13
47
|
# Generic streaming parser that auto-detects the list type from the
|
|
14
48
|
# first effective FORMAT line and yields events for any DSV7 list.
|
|
15
|
-
#
|
|
16
|
-
# The first event is always
|
|
17
|
-
#
|
|
49
|
+
#
|
|
50
|
+
# The first event is always `:format` with payload
|
|
51
|
+
# `{ list_type: <String>, version: <String> }`.
|
|
52
|
+
#
|
|
53
|
+
# @api public
|
|
54
|
+
# @since 7.0.0
|
|
55
|
+
# @overload parse(input, &block)
|
|
56
|
+
# @param input [IO, String] An IO, a file path String, or a String with file content
|
|
57
|
+
# @yield [type, payload, line_number] Emitted for each event
|
|
58
|
+
# @yieldparam type [Symbol] Event type (:format, :element, :end)
|
|
59
|
+
# @yieldparam payload [Hash, nil] Event payload
|
|
60
|
+
# @yieldparam line_number [Integer] 1-based line number of the event
|
|
61
|
+
# @return [void]
|
|
62
|
+
# @overload parse(input)
|
|
63
|
+
# @param input [IO, String]
|
|
64
|
+
# @return [Enumerator] Enumerator over `[type, payload, line_number]`
|
|
65
|
+
# @see Dsv7::Validator
|
|
66
|
+
# @raise [Dsv7::Parser::Error] when the first effective line is not FORMAT
|
|
67
|
+
# @raise [ArgumentError] if the input type is unsupported
|
|
18
68
|
def self.parse(input, &block)
|
|
19
69
|
enum = Enumerator.new { |y| Engine.stream_any(input, y) }
|
|
20
70
|
return enum.each(&block) if block_given?
|
|
@@ -23,8 +73,23 @@ module Dsv7
|
|
|
23
73
|
end
|
|
24
74
|
|
|
25
75
|
# Streaming parser for Wettkampfdefinitionsliste (WKDL).
|
|
26
|
-
# Yields [:format|:element|:end, payload, line_number].
|
|
27
76
|
# Performs inline comment stripping, tolerates BOM, and scrubs UTF-8.
|
|
77
|
+
#
|
|
78
|
+
# @api public
|
|
79
|
+
# @since 7.0.0
|
|
80
|
+
# @overload parse_wettkampfdefinitionsliste(input, &block)
|
|
81
|
+
# @param input [IO, String]
|
|
82
|
+
# @yield [type, payload, line_number]
|
|
83
|
+
# @yieldparam type [Symbol] Event type (:format, :element, :end)
|
|
84
|
+
# @yieldparam payload [Hash, nil]
|
|
85
|
+
# @yieldparam line_number [Integer]
|
|
86
|
+
# @return [void]
|
|
87
|
+
# @overload parse_wettkampfdefinitionsliste(input)
|
|
88
|
+
# @param input [IO, String]
|
|
89
|
+
# @return [Enumerator]
|
|
90
|
+
# @raise [Dsv7::Parser::Error] if the list type is not WKDL
|
|
91
|
+
# @raise [ArgumentError] if the input type is unsupported
|
|
92
|
+
# @see Dsv7::Validator
|
|
28
93
|
def self.parse_wettkampfdefinitionsliste(input, &block)
|
|
29
94
|
enum = Enumerator.new { |y| Engine.stream_list(input, y, 'Wettkampfdefinitionsliste') }
|
|
30
95
|
return enum.each(&block) if block_given?
|
|
@@ -33,8 +98,20 @@ module Dsv7
|
|
|
33
98
|
end
|
|
34
99
|
|
|
35
100
|
# Streaming parser for Vereinsmeldeliste (VML).
|
|
36
|
-
# Same contract as parse_wettkampfdefinitionsliste, but expects
|
|
37
|
-
# FORMAT:Vereinsmeldeliste;7
|
|
101
|
+
# Same contract as {parse_wettkampfdefinitionsliste}, but expects
|
|
102
|
+
# `FORMAT:Vereinsmeldeliste;7;` as the first effective line.
|
|
103
|
+
# @api public
|
|
104
|
+
# @since 7.0.0
|
|
105
|
+
# @overload parse_vereinsmeldeliste(input, &block)
|
|
106
|
+
# @param input [IO, String]
|
|
107
|
+
# @yield [type, payload, line_number]
|
|
108
|
+
# @return [void]
|
|
109
|
+
# @overload parse_vereinsmeldeliste(input)
|
|
110
|
+
# @param input [IO, String]
|
|
111
|
+
# @return [Enumerator]
|
|
112
|
+
# @raise [Dsv7::Parser::Error] if the list type is not VML
|
|
113
|
+
# @raise [ArgumentError] if the input type is unsupported
|
|
114
|
+
# @see Dsv7::Validator
|
|
38
115
|
def self.parse_vereinsmeldeliste(input, &block)
|
|
39
116
|
enum = Enumerator.new { |y| Engine.stream_list(input, y, 'Vereinsmeldeliste') }
|
|
40
117
|
return enum.each(&block) if block_given?
|
|
@@ -44,7 +121,19 @@ module Dsv7
|
|
|
44
121
|
|
|
45
122
|
# Streaming parser for Wettkampfergebnisliste (ERG).
|
|
46
123
|
# Same contract as the other parse_* methods, but expects
|
|
47
|
-
# FORMAT:Wettkampfergebnisliste;7
|
|
124
|
+
# `FORMAT:Wettkampfergebnisliste;7;` as the first effective line.
|
|
125
|
+
# @api public
|
|
126
|
+
# @since 7.0.0
|
|
127
|
+
# @overload parse_wettkampfergebnisliste(input, &block)
|
|
128
|
+
# @param input [IO, String]
|
|
129
|
+
# @yield [type, payload, line_number]
|
|
130
|
+
# @return [void]
|
|
131
|
+
# @overload parse_wettkampfergebnisliste(input)
|
|
132
|
+
# @param input [IO, String]
|
|
133
|
+
# @return [Enumerator]
|
|
134
|
+
# @raise [Dsv7::Parser::Error] if the list type is not ERG
|
|
135
|
+
# @raise [ArgumentError] if the input type is unsupported
|
|
136
|
+
# @see Dsv7::Validator
|
|
48
137
|
def self.parse_wettkampfergebnisliste(input, &block)
|
|
49
138
|
enum = Enumerator.new { |y| Engine.stream_list(input, y, 'Wettkampfergebnisliste') }
|
|
50
139
|
return enum.each(&block) if block_given?
|
|
@@ -54,13 +143,26 @@ module Dsv7
|
|
|
54
143
|
|
|
55
144
|
# Streaming parser for Vereinsergebnisliste (VRL).
|
|
56
145
|
# Same contract as the other parse_* methods, but expects
|
|
57
|
-
# FORMAT:Vereinsergebnisliste;7
|
|
146
|
+
# `FORMAT:Vereinsergebnisliste;7;` as the first effective line.
|
|
147
|
+
# @api public
|
|
148
|
+
# @since 7.0.0
|
|
149
|
+
# @overload parse_vereinsergebnisliste(input, &block)
|
|
150
|
+
# @param input [IO, String]
|
|
151
|
+
# @yield [type, payload, line_number]
|
|
152
|
+
# @return [void]
|
|
153
|
+
# @overload parse_vereinsergebnisliste(input)
|
|
154
|
+
# @param input [IO, String]
|
|
155
|
+
# @return [Enumerator]
|
|
156
|
+
# @raise [Dsv7::Parser::Error] if the list type is not VRL
|
|
157
|
+
# @raise [ArgumentError] if the input type is unsupported
|
|
158
|
+
# @see Dsv7::Validator
|
|
58
159
|
def self.parse_vereinsergebnisliste(input, &block)
|
|
59
160
|
enum = Enumerator.new { |y| Engine.stream_list(input, y, 'Vereinsergebnisliste') }
|
|
60
161
|
return enum.each(&block) if block_given?
|
|
61
162
|
|
|
62
163
|
enum
|
|
63
164
|
end
|
|
165
|
+
# @!endgroup
|
|
64
166
|
# no additional private class methods
|
|
65
167
|
end
|
|
66
168
|
end
|
data/lib/dsv7/stream.rb
CHANGED
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Dsv7
|
|
4
|
+
##
|
|
5
|
+
# Low‑level IO helpers for streaming DSV7 content.
|
|
6
|
+
#
|
|
7
|
+
# Responsibilities
|
|
8
|
+
# - Binary mode, BOM detection, and UTF‑8 normalization.
|
|
9
|
+
# - Per‑line sanitization and CR/LF handling.
|
|
10
|
+
# - Inline single‑line comment removal using the `(* ... *)` syntax.
|
|
11
|
+
#
|
|
12
|
+
# These helpers are shared by both the validator and the parser.
|
|
13
|
+
#
|
|
14
|
+
# @api private
|
|
4
15
|
module Stream
|
|
5
16
|
module_function
|
|
6
17
|
|
|
7
18
|
# Puts IO into binary mode when possible (no-op for StringIO)
|
|
19
|
+
# @param io [IO]
|
|
20
|
+
# @return [void]
|
|
8
21
|
def binmode_if_possible(io)
|
|
9
22
|
io.binmode
|
|
10
23
|
rescue StandardError
|
|
@@ -14,6 +27,8 @@ module Dsv7
|
|
|
14
27
|
# Reads potential UTF-8 BOM from the start of IO.
|
|
15
28
|
# Returns true if a BOM was found (and consumed), false otherwise.
|
|
16
29
|
# If no BOM was found, unread the peeked bytes back into the IO.
|
|
30
|
+
# @param io [IO]
|
|
31
|
+
# @return [Boolean]
|
|
17
32
|
def read_bom?(io)
|
|
18
33
|
head = io.read(3)
|
|
19
34
|
return false if head.nil? || head.empty?
|
|
@@ -29,6 +44,9 @@ module Dsv7
|
|
|
29
44
|
# Normalizes a raw line by trimming trailing LF/CR and forcing UTF-8.
|
|
30
45
|
# If invalid encoding is detected, it scrubs replacement chars and
|
|
31
46
|
# calls the optional on_invalid callback.
|
|
47
|
+
# @param raw [String]
|
|
48
|
+
# @param on_invalid [Proc,nil]
|
|
49
|
+
# @return [String]
|
|
32
50
|
def sanitize_line(raw, on_invalid: nil)
|
|
33
51
|
s = raw.delete_suffix("\n").delete_suffix("\r")
|
|
34
52
|
s.force_encoding(Encoding::UTF_8)
|
|
@@ -39,6 +57,8 @@ module Dsv7
|
|
|
39
57
|
end
|
|
40
58
|
|
|
41
59
|
# Removes inline single-line comments in the form: (* ... *)
|
|
60
|
+
# @param line [String]
|
|
61
|
+
# @return [String]
|
|
42
62
|
def strip_inline_comment(line)
|
|
43
63
|
return line unless line.include?('(*') && line.include?('*)')
|
|
44
64
|
|
|
@@ -47,6 +67,12 @@ module Dsv7
|
|
|
47
67
|
|
|
48
68
|
# Iterates sanitized lines, yielding [line, line_number].
|
|
49
69
|
# Returns true if any CRLF lines were observed.
|
|
70
|
+
# @param io [IO]
|
|
71
|
+
# @param on_invalid [Proc,nil]
|
|
72
|
+
# @yield [line, line_number]
|
|
73
|
+
# @yieldparam line [String]
|
|
74
|
+
# @yieldparam line_number [Integer]
|
|
75
|
+
# @return [Boolean] whether any CRLF lines were observed
|
|
50
76
|
def each_sanitized_line(io, on_invalid: nil)
|
|
51
77
|
had_crlf = false
|
|
52
78
|
line_number = 0
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# Element cardinality validation for each list type.
|
|
4
|
+
#
|
|
5
|
+
# Each validator receives the shared Result and a Hash of observed element
|
|
6
|
+
# counts, then asserts required presence and max occurrences according to the
|
|
7
|
+
# current understanding of the spec.
|
|
8
|
+
|
|
3
9
|
module Dsv7
|
|
4
10
|
class Validator
|
|
5
11
|
# Validates Wettkampfdefinitionsliste element cardinalities
|
data/lib/dsv7/validator/core.rb
CHANGED
|
@@ -7,12 +7,30 @@ require_relative 'line_analyzer'
|
|
|
7
7
|
module Dsv7
|
|
8
8
|
class Validator
|
|
9
9
|
# Core pipeline for validator: encoding + line parsing
|
|
10
|
+
##
|
|
11
|
+
# Core validation pipeline.
|
|
12
|
+
#
|
|
13
|
+
# Implements the IO/line streaming for the validator:
|
|
14
|
+
# - puts IO in binary mode and detects BOM
|
|
15
|
+
# - normalizes lines to UTF‑8 and tracks CRLF presence
|
|
16
|
+
# - strips inline comments and delegates per‑line logic to LineAnalyzer
|
|
17
|
+
# - adds a filename warning if the provided path does not match the guidance
|
|
18
|
+
#
|
|
19
|
+
# Notes for maintainers
|
|
20
|
+
# - Keep this class side‑effect free beyond writing to `Result`.
|
|
21
|
+
# - Avoid accumulating state; process line‑by‑line to preserve streaming.
|
|
22
|
+
#
|
|
23
|
+
# @api private
|
|
10
24
|
class Core
|
|
25
|
+
# @param result [Dsv7::Validator::Result]
|
|
26
|
+
# @param filename [String, nil]
|
|
11
27
|
def initialize(result, filename)
|
|
12
28
|
@result = result
|
|
13
29
|
@filename = filename
|
|
14
30
|
end
|
|
15
31
|
|
|
32
|
+
# @param io [IO]
|
|
33
|
+
# @return [Dsv7::Validator::Result]
|
|
16
34
|
def call_io(io)
|
|
17
35
|
Dsv7::Stream.binmode_if_possible(io)
|
|
18
36
|
check_bom_and_rewind(io)
|
|
@@ -9,12 +9,22 @@ require_relative 'schemas/vrl_schema'
|
|
|
9
9
|
|
|
10
10
|
module Dsv7
|
|
11
11
|
class Validator
|
|
12
|
+
##
|
|
13
|
+
# Streaming line analyzer.
|
|
14
|
+
#
|
|
15
|
+
# Orchestrates validation once lines have been sanitized and comments stripped.
|
|
16
|
+
# Tracks the first effective FORMAT line, enforces the final DATEIENDE, and
|
|
17
|
+
# dispatches element lines to list‑specific schema/type checks and cardinality
|
|
18
|
+
# tracking. All findings are written into the shared {Dsv7::Validator::Result} instance.
|
|
19
|
+
#
|
|
20
|
+
# @api private
|
|
12
21
|
class LineAnalyzer
|
|
13
22
|
include LineAnalyzerWk
|
|
14
23
|
include LineAnalyzerVml
|
|
15
24
|
include LineAnalyzerErg
|
|
16
25
|
include LineAnalyzerVrl
|
|
17
26
|
|
|
27
|
+
# @param result [Dsv7::Validator::Result]
|
|
18
28
|
def initialize(result)
|
|
19
29
|
@result = result
|
|
20
30
|
@effective_index = 0
|
|
@@ -24,6 +34,7 @@ module Dsv7
|
|
|
24
34
|
init_schemas_and_counters
|
|
25
35
|
end
|
|
26
36
|
|
|
37
|
+
# Initialize schemas and element counters used during streaming.
|
|
27
38
|
def init_schemas_and_counters
|
|
28
39
|
@wk_elements = Hash.new(0)
|
|
29
40
|
@wk_schema = WkSchema.new(@result)
|
|
@@ -35,9 +46,13 @@ module Dsv7
|
|
|
35
46
|
@vrl_schema = VrlSchema.new(@result)
|
|
36
47
|
end
|
|
37
48
|
|
|
49
|
+
# Process a raw input line with its 1-based line number.
|
|
50
|
+
# @param line [String]
|
|
51
|
+
# @param line_number [Integer]
|
|
52
|
+
# @return [void]
|
|
38
53
|
def process_line(line, line_number)
|
|
39
54
|
check_comment_balance(line, line_number)
|
|
40
|
-
trimmed = strip_inline_comment(line)
|
|
55
|
+
trimmed = Dsv7::Stream.strip_inline_comment(line).strip
|
|
41
56
|
return if trimmed.empty?
|
|
42
57
|
|
|
43
58
|
@effective_index += 1
|
|
@@ -47,6 +62,8 @@ module Dsv7
|
|
|
47
62
|
handle_content_line(trimmed, line_number)
|
|
48
63
|
end
|
|
49
64
|
|
|
65
|
+
# Finalize the analysis and run post/summary checks.
|
|
66
|
+
# @return [void]
|
|
50
67
|
def finish
|
|
51
68
|
post_validate_positions
|
|
52
69
|
validate_wk_list_elements if @result.list_type == 'Wettkampfdefinitionsliste'
|
|
@@ -57,6 +74,9 @@ module Dsv7
|
|
|
57
74
|
|
|
58
75
|
private
|
|
59
76
|
|
|
77
|
+
# Internal helper to set up schemas and counters
|
|
78
|
+
private :init_schemas_and_counters
|
|
79
|
+
|
|
60
80
|
def check_comment_balance(line, line_number)
|
|
61
81
|
return unless line.include?('(*') || line.include?('*)')
|
|
62
82
|
|
|
@@ -72,10 +92,6 @@ module Dsv7
|
|
|
72
92
|
check_format_line(trimmed, line_number)
|
|
73
93
|
end
|
|
74
94
|
|
|
75
|
-
def strip_inline_comment(line)
|
|
76
|
-
Dsv7::Stream.strip_inline_comment(line).strip
|
|
77
|
-
end
|
|
78
|
-
|
|
79
95
|
def check_format_line(trimmed, line_number)
|
|
80
96
|
m = Dsv7::Lex.parse_format(trimmed)
|
|
81
97
|
return format_error(line_number) unless m
|
|
@@ -7,6 +7,19 @@ module Dsv7
|
|
|
7
7
|
class Validator
|
|
8
8
|
# Handles line-by-line structural checks for WKDL-specific logic
|
|
9
9
|
module LineAnalyzerWk
|
|
10
|
+
##
|
|
11
|
+
# List‑specific analyzer mixins.
|
|
12
|
+
#
|
|
13
|
+
# These modules encapsulate per‑list tracking and validation methods used by
|
|
14
|
+
# {Dsv7::Validator::LineAnalyzer}. Each provides three responsibilities for its list type:
|
|
15
|
+
# - track_*_element: counts element occurrences for cardinality checks
|
|
16
|
+
# - validate_*_list_elements: validates the observed counts at finish
|
|
17
|
+
# - validate_*_line: validates a single element’s attributes via the schema
|
|
18
|
+
#
|
|
19
|
+
# @api private
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
10
23
|
def track_wk_element(trimmed)
|
|
11
24
|
return unless @result.list_type == 'Wettkampfdefinitionsliste'
|
|
12
25
|
|
|
@@ -39,6 +52,10 @@ module Dsv7
|
|
|
39
52
|
|
|
40
53
|
# Handles line-by-line structural checks for VML-specific logic
|
|
41
54
|
module LineAnalyzerVml
|
|
55
|
+
# @api private
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
42
59
|
def track_vml_element(trimmed)
|
|
43
60
|
return unless @result.list_type == 'Vereinsmeldeliste'
|
|
44
61
|
|
|
@@ -71,6 +88,10 @@ module Dsv7
|
|
|
71
88
|
|
|
72
89
|
# Handles line-by-line checks for Wettkampfergebnisliste
|
|
73
90
|
module LineAnalyzerErg
|
|
91
|
+
# @api private
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
74
95
|
def track_erg_element(trimmed)
|
|
75
96
|
return unless @result.list_type == 'Wettkampfergebnisliste'
|
|
76
97
|
|
|
@@ -105,6 +126,10 @@ module Dsv7
|
|
|
105
126
|
|
|
106
127
|
# Handles line-by-line checks for Vereinsergebnisliste
|
|
107
128
|
module LineAnalyzerVrl
|
|
129
|
+
# @api private
|
|
130
|
+
|
|
131
|
+
private
|
|
132
|
+
|
|
108
133
|
def track_vrl_element(trimmed)
|
|
109
134
|
return unless @result.list_type == 'Vereinsergebnisliste'
|
|
110
135
|
|
|
@@ -1,9 +1,31 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
##
|
|
4
|
+
# Validation result container.
|
|
5
|
+
#
|
|
6
|
+
# Collects errors and warnings during a validation run and exposes
|
|
7
|
+
# `list_type`/`version` after a valid FORMAT line is seen. `valid?` is true
|
|
8
|
+
# iff there are no errors; warnings never affect validity.
|
|
9
|
+
#
|
|
10
|
+
# Message stability
|
|
11
|
+
# - Keep message texts stable where possible; tests rely on them.
|
|
12
|
+
# - Include line numbers when relevant to aid debugging.
|
|
13
|
+
|
|
3
14
|
module Dsv7
|
|
4
15
|
class Validator
|
|
5
16
|
# Result container for validation
|
|
17
|
+
#
|
|
18
|
+
# @api public
|
|
19
|
+
# @since 7.0.0
|
|
6
20
|
class Result
|
|
21
|
+
# @!attribute [r] errors
|
|
22
|
+
# @return [Array<String>] Collected human-readable error messages
|
|
23
|
+
# @!attribute [r] warnings
|
|
24
|
+
# @return [Array<String>] Collected warnings; do not affect validity
|
|
25
|
+
# @!attribute [r] list_type
|
|
26
|
+
# @return [String, nil] List type after parsing the FORMAT line
|
|
27
|
+
# @!attribute [r] version
|
|
28
|
+
# @return [String, nil] Format version after parsing the FORMAT line
|
|
7
29
|
attr_reader :errors, :warnings, :list_type, :version
|
|
8
30
|
|
|
9
31
|
def initialize
|
|
@@ -13,19 +35,31 @@ module Dsv7
|
|
|
13
35
|
@version = nil
|
|
14
36
|
end
|
|
15
37
|
|
|
38
|
+
# Add an error message.
|
|
39
|
+
# @param message [String]
|
|
40
|
+
# @return [void]
|
|
16
41
|
def add_error(message)
|
|
17
42
|
@errors << message
|
|
18
43
|
end
|
|
19
44
|
|
|
45
|
+
# Add a warning message.
|
|
46
|
+
# @param message [String]
|
|
47
|
+
# @return [void]
|
|
20
48
|
def add_warning(message)
|
|
21
49
|
@warnings << message
|
|
22
50
|
end
|
|
23
51
|
|
|
52
|
+
# Set FORMAT metadata after a valid FORMAT line was observed.
|
|
53
|
+
# @param list_type [String]
|
|
54
|
+
# @param version [String]
|
|
55
|
+
# @return [void]
|
|
24
56
|
def set_format(list_type, version)
|
|
25
57
|
@list_type = list_type
|
|
26
58
|
@version = version
|
|
27
59
|
end
|
|
28
60
|
|
|
61
|
+
# Whether the validation run produced no errors.
|
|
62
|
+
# @return [Boolean]
|
|
29
63
|
def valid?
|
|
30
64
|
@errors.empty?
|
|
31
65
|
end
|
|
@@ -2,11 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
module Dsv7
|
|
4
4
|
class Validator
|
|
5
|
+
##
|
|
6
|
+
# Base class for per‑list schemas.
|
|
7
|
+
#
|
|
8
|
+
# A concrete schema class defines a `SCHEMAS` Hash mapping element names to an
|
|
9
|
+
# Array of attribute specs. Each attribute spec is a tuple:
|
|
10
|
+
# `[type, required, opts=nil]`
|
|
11
|
+
# where `type` corresponds to a `check_<type>` method mixed in from the
|
|
12
|
+
# type‑check modules, `required` is a boolean, and `opts` can be used by a
|
|
13
|
+
# specific checker.
|
|
14
|
+
#
|
|
15
|
+
# Cross‑field/element rules may be implemented by overriding
|
|
16
|
+
# `validate_cross_rules(name, attrs, line_number)`.
|
|
17
|
+
#
|
|
18
|
+
# Documentation tips when adding/adjusting schemas:
|
|
19
|
+
# - Copy the attribute count and types from the spec and real‑world examples.
|
|
20
|
+
# - Clearly mark intentionally deferred or ambiguous elements in commit msgs.
|
|
21
|
+
# - Add both positive and negative tests for each element and datatype.
|
|
22
|
+
#
|
|
23
|
+
# @see specification/dsv7/dsv7_specification.md Specification reference
|
|
24
|
+
# @api private
|
|
5
25
|
class SchemaBase
|
|
6
26
|
def initialize(result)
|
|
7
27
|
@result = result
|
|
8
28
|
end
|
|
9
29
|
|
|
30
|
+
# Validate a single element against the schema map.
|
|
31
|
+
# @param name [String]
|
|
32
|
+
# @param attrs [Array<String>]
|
|
33
|
+
# @param line_number [Integer]
|
|
34
|
+
# @return [void]
|
|
10
35
|
def validate_element(name, attrs, line_number)
|
|
11
36
|
schema = self.class::SCHEMAS[name]
|
|
12
37
|
return unless schema
|
|
@@ -33,6 +58,7 @@ module Dsv7
|
|
|
33
58
|
end
|
|
34
59
|
end
|
|
35
60
|
|
|
61
|
+
# @return [void]
|
|
36
62
|
def add_error(msg)
|
|
37
63
|
@result.add_error(msg)
|
|
38
64
|
end
|
|
@@ -5,7 +5,11 @@ require_relative 'base'
|
|
|
5
5
|
|
|
6
6
|
module Dsv7
|
|
7
7
|
class Validator
|
|
8
|
-
# Validates Wettkampfergebnisliste attribute counts and datatypes
|
|
8
|
+
# Validates Wettkampfergebnisliste attribute counts and datatypes.
|
|
9
|
+
# Accepts synonymous element names found in the wild
|
|
10
|
+
# (e.g., STAFFELERGEBNIS/STERGEBNIS).
|
|
11
|
+
#
|
|
12
|
+
# @api private
|
|
9
13
|
class ErgSchema < SchemaBase
|
|
10
14
|
include WkTypeChecks
|
|
11
15
|
|
|
@@ -5,7 +5,9 @@ require_relative 'base'
|
|
|
5
5
|
|
|
6
6
|
module Dsv7
|
|
7
7
|
class Validator
|
|
8
|
-
# Validates Vereinsmeldeliste attribute counts and datatypes
|
|
8
|
+
# Validates Vereinsmeldeliste attribute counts and datatypes.
|
|
9
|
+
#
|
|
10
|
+
# @api private
|
|
9
11
|
class VmlSchema < SchemaBase
|
|
10
12
|
include WkTypeChecks
|
|
11
13
|
|
|
@@ -5,7 +5,11 @@ require_relative 'base'
|
|
|
5
5
|
|
|
6
6
|
module Dsv7
|
|
7
7
|
class Validator
|
|
8
|
-
# Validates Vereinsergebnisliste attribute counts and datatypes
|
|
8
|
+
# Validates Vereinsergebnisliste attribute counts and datatypes.
|
|
9
|
+
# Accepts synonymous element names found in the wild
|
|
10
|
+
# (e.g., STAFFELERGEBNIS/STERGEBNIS).
|
|
11
|
+
#
|
|
12
|
+
# @api private
|
|
9
13
|
class VrlSchema < SchemaBase
|
|
10
14
|
include WkTypeChecks
|
|
11
15
|
|
|
@@ -5,7 +5,12 @@ require_relative 'base'
|
|
|
5
5
|
|
|
6
6
|
module Dsv7
|
|
7
7
|
class Validator
|
|
8
|
-
# Validates Wettkampfdefinitionsliste attribute counts and datatypes
|
|
8
|
+
# Validates Wettkampfdefinitionsliste attribute counts and datatypes.
|
|
9
|
+
#
|
|
10
|
+
# The `SCHEMAS` constant defines the exact attribute counts and types per
|
|
11
|
+
# element according to the current spec interpretation.
|
|
12
|
+
#
|
|
13
|
+
# @api private
|
|
9
14
|
class WkSchema < SchemaBase
|
|
10
15
|
include WkTypeChecks
|
|
11
16
|
|
|
@@ -49,6 +54,8 @@ module Dsv7
|
|
|
49
54
|
'MELDEGELD' => [[:meldegeld_typ, true], [:betrag, true], [:zahl, false]]
|
|
50
55
|
}.freeze
|
|
51
56
|
|
|
57
|
+
private
|
|
58
|
+
|
|
52
59
|
def validate_cross_rules(name, attrs, line_number)
|
|
53
60
|
return unless name == 'MELDEGELD'
|
|
54
61
|
|
|
@@ -2,7 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
module Dsv7
|
|
4
4
|
class Validator
|
|
5
|
+
##
|
|
6
|
+
# Common datatype checks shared across lists.
|
|
7
|
+
#
|
|
8
|
+
# Implementations follow the spec’s informal definitions:
|
|
9
|
+
# - ZK: arbitrary UTF‑8 string (already scrubbed by the stream layer)
|
|
10
|
+
# - Zahl: integer (only digits)
|
|
11
|
+
# - Betrag: monetary amount in the form `x,yy`
|
|
12
|
+
# - Einzelstrecke: distance (1..25000) or 0 where permitted
|
|
13
|
+
#
|
|
14
|
+
# @see specification/dsv7/dsv7_specification.md Datatypes overview
|
|
15
|
+
# @api private
|
|
5
16
|
module WkTypeChecksCommon
|
|
17
|
+
private
|
|
18
|
+
|
|
6
19
|
def check_zk(_name, _index, _val, _line_number, _opts = nil)
|
|
7
20
|
# any string (already UTF-8 scrubbed); nothing to do
|
|
8
21
|
end
|
|
@@ -4,7 +4,19 @@ require 'date'
|
|
|
4
4
|
|
|
5
5
|
module Dsv7
|
|
6
6
|
class Validator
|
|
7
|
+
##
|
|
8
|
+
# Date and time datatype checks.
|
|
9
|
+
#
|
|
10
|
+
# Enforces textual formats before validating value ranges:
|
|
11
|
+
# - Datum: TT.MM.JJJJ (validated via Date.strptime)
|
|
12
|
+
# - Uhrzeit: HH:MM (0..23, 0..59)
|
|
13
|
+
# - Zeit: HH:MM:SS,hh (0..23, 0..59, 0..59, 0..99)
|
|
14
|
+
#
|
|
15
|
+
# @see specification/dsv7/dsv7_specification.md Date/time formats
|
|
16
|
+
# @api private
|
|
7
17
|
module WkTypeChecksDateTime
|
|
18
|
+
private
|
|
19
|
+
|
|
8
20
|
def check_datum(name, idx, val, line_number, _opts = nil)
|
|
9
21
|
return add_error(datum_format_error(name, idx, val, line_number)) unless
|
|
10
22
|
val.match?(/^\d{2}\.\d{2}\.\d{4}$/)
|
|
@@ -2,7 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
module Dsv7
|
|
4
4
|
class Validator
|
|
5
|
+
##
|
|
6
|
+
# Enum/group checks (part 1): Bahnlänge, Zeitmessung, Land, etc.
|
|
7
|
+
#
|
|
8
|
+
# These normalize expectations found in the spec and examples and produce
|
|
9
|
+
# actionable error messages that include allowed values.
|
|
10
|
+
#
|
|
11
|
+
# @see specification/dsv7/dsv7_specification.md Enumerations overview
|
|
12
|
+
# @api private
|
|
5
13
|
module WkTypeChecksEnums1
|
|
14
|
+
private
|
|
15
|
+
|
|
6
16
|
def check_bahnl(name, idx, val, line_number, _opts = nil)
|
|
7
17
|
allowed = %w[16 20 25 33 50 FW X]
|
|
8
18
|
return if allowed.include?(val)
|
|
@@ -2,7 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
module Dsv7
|
|
4
4
|
class Validator
|
|
5
|
+
##
|
|
6
|
+
# Enum/group checks (part 2): Technik, Ausübung, Geschlecht, Wertungstyp,
|
|
7
|
+
# JG/AK, Meldegeldtypen, Reaktionsart, Nachtragskennzeichen, u. a.
|
|
8
|
+
#
|
|
9
|
+
# Keep allowed lists centralized here for clarity and reuse across schemas.
|
|
10
|
+
#
|
|
11
|
+
# @see specification/dsv7/dsv7_specification.md Enumerations overview
|
|
12
|
+
# @api private
|
|
5
13
|
module WkTypeChecksEnums2
|
|
14
|
+
private
|
|
15
|
+
|
|
6
16
|
def check_technik(name, idx, val, line_number, _opts = nil)
|
|
7
17
|
return if %w[F R B S L X].include?(val)
|
|
8
18
|
|
data/lib/dsv7/validator/types.rb
CHANGED
|
@@ -7,6 +7,14 @@ require_relative 'types/enums2'
|
|
|
7
7
|
|
|
8
8
|
module Dsv7
|
|
9
9
|
class Validator
|
|
10
|
+
##
|
|
11
|
+
# Aggregates type‑check mixins used by schemas.
|
|
12
|
+
#
|
|
13
|
+
# Each `check_<type>(name, index, value, line_number, opts)` method is
|
|
14
|
+
# expected to either accept the value or call `add_error(message)` on the
|
|
15
|
+
# including schema to record a validation error with context.
|
|
16
|
+
#
|
|
17
|
+
# @api private
|
|
10
18
|
module WkTypeChecks
|
|
11
19
|
include WkTypeChecksCommon
|
|
12
20
|
include WkTypeChecksDateTime
|