1
1
module Noid
2
2
class Template
3
- attr_reader :template
3
+ attr_reader :template , :prefix , :generator , :characters
4
+
5
+ VALID_PATTERN = /\A (.*)\. ([rsz])([ed]+)(k?)\Z /
4
6
5
7
# @param [String] template A Template is a coded string of the form Prefix.Mask that governs how identifiers will be minted.
6
8
def initialize ( template )
7
9
@template = template
10
+ parse!
8
11
end
9
12
10
13
def mint ( n )
@@ -15,93 +18,106 @@ def mint(n)
15
18
str
16
19
end
17
20
21
+ ##
22
+ # Is the string valid against this template string and checksum?
23
+ # @param [String] str
24
+ # @return bool
18
25
def valid? ( str )
19
- return false unless str [ 0 ..prefix . length ] == prefix
20
-
21
- if generator == 'z'
22
- str [ prefix . length ..-1 ] . length > 2
23
- else
24
- str [ prefix . length ..-1 ] . length == characters . length
25
- end
26
-
27
- characters . split ( '' ) . each_with_index do |c , i |
28
- case c
29
- when 'e'
30
- return false unless Noid ::XDIGIT . include? str [ prefix . length + i ]
31
- when 'd'
32
- return false unless str [ prefix . length + i ] =~ /\d /
33
- end
34
- end
35
-
36
- return false unless checkdigit ( str [ 0 ..-2 ] ) == str . split ( '' ) . last if checkdigit?
37
-
26
+ match = validation_regex . match ( str )
27
+ return false if match . nil?
28
+ return checkdigit ( match [ 1 ] ) == match [ 3 ] if checkdigit?
38
29
true
39
30
end
40
31
41
32
##
42
- # identifier prefix string
43
- def prefix
44
- @prefix ||= @template . split ( '.' ) . first
33
+ # calculate a checkdigit for the str
34
+ # @param [String] str
35
+ # @return [String] checkdigit
36
+ def checkdigit ( str )
37
+ Noid ::XDIGIT [ str . split ( '' ) . map { |x | Noid ::XDIGIT . index ( x ) . to_i } . each_with_index . map { |n , idx | n * ( idx + 1 ) } . inject { |sum , n | sum + n } % Noid ::XDIGIT . length ]
45
38
end
46
39
47
40
##
48
- # identifier mask string
49
- def mask
50
- @mask ||= @template . split ( '.' ) . last
41
+ # minimum sequence value
42
+ def min
43
+ @min ||= 0
51
44
end
52
45
53
46
##
54
- # generator type to use: r, s, z
55
- def generator
56
- @generator ||= mask [ 0 ..0 ]
47
+ # maximum sequence value for the template
48
+ def max
49
+ @max ||= if generator == 'z'
50
+ nil
51
+ else
52
+ size_list . inject ( 1 ) { |total , x | total * x }
53
+ end
57
54
end
58
55
56
+ protected
57
+
59
58
##
60
- # sequence pattern: e (extended), d (digit)
61
- def characters
62
- @characters ||= begin
63
- if checkdigit?
64
- mask [ 1 ..-2 ]
65
- else
66
- mask [ 1 ..-1 ]
67
- end
68
- end
59
+ # A noid has the structure (prefix)(code)(checkdigit)
60
+ # the regexp has the following captures
61
+ # 1 - the prefix and the code
62
+ # 2 - the changing id characters (not the prefix and not the checkdigit)
63
+ # 3 - the checkdigit, if there is one. This field is missing if there is no checkdigit
64
+ def validation_regex
65
+ @validation_regex ||= begin
66
+ character_pattern = ''
67
+ # the first character in the mask after the type character is the most significant
68
+ # acc. to the Noid spec (p.9):
69
+ # https://wiki.ucop.edu/display/Curation/NOID?preview=/16744482/16973835/noid.pdf
70
+ character_pattern += character_to_pattern ( character_list . first ) + "*" if generator == 'z'
71
+ character_pattern += character_list . map { |c | character_to_pattern ( c ) } . join
72
+
73
+ %r{\A (#{ Regexp . escape ( prefix ) } (#{ character_pattern } ))(#{ character_to_pattern ( 'k' ) if checkdigit? } )\Z }
74
+ end
69
75
end
70
76
71
77
##
72
- # should generated identifiers have a checkdigit?
73
- def checkdigit?
74
- mask . split ( '' ) . last == 'k'
78
+ # parse template and put the results into instance variables
79
+ # raise an exception if there is a parse error
80
+ def parse!
81
+ match = VALID_PATTERN . match ( template )
82
+ raise Noid ::TemplateError , "Malformed noid template '#{ template } '" unless match
83
+ @prefix = match [ 1 ]
84
+ @generator = match [ 2 ]
85
+ @characters = match [ 3 ]
86
+ @checkdigit = ( match [ 4 ] == 'k' )
75
87
end
76
88
77
- ##
78
- # calculate a checkdigit for the str
79
- # @param [String] str
80
- # @return [String] checkdigit
81
- def checkdigit ( str )
82
- Noid ::XDIGIT [ str . split ( '' ) . map { |x | Noid ::XDIGIT . index ( x ) . to_i } . each_with_index . map { |n , idx | n * ( idx + 1 ) } . inject { |sum , n | sum + n } % Noid ::XDIGIT . length ]
89
+ def xdigit_pattern
90
+ @xdigit_pattern ||= '[' + Noid ::XDIGIT . join ( '' ) + ']'
83
91
end
84
92
85
- ##
86
- # minimum sequence value
87
- def min
88
- @min ||= 0
93
+ def character_to_pattern ( c )
94
+ case c
95
+ when 'e' , 'k'
96
+ xdigit_pattern
97
+ when 'd'
98
+ '\d'
99
+ else
100
+ ''
101
+ end
89
102
end
90
103
91
104
##
92
- # maximum sequence value for the template
93
- def max
94
- @max ||= begin
95
- case generator
96
- when 'z'
97
- nil
98
- else
99
- characters . split ( '' ) . map { |x | character_space ( x ) } . compact . inject ( 1 ) { |total , x | total * x }
100
- end
101
- end
105
+ # Return a list giving the number of possible characters at each position
106
+ def size_list
107
+ @size_list ||= character_list . map { |c | character_space ( c ) }
102
108
end
103
109
104
- protected
110
+ def character_list
111
+ characters . split ( '' )
112
+ end
113
+
114
+ def mask
115
+ generator + characters
116
+ end
117
+
118
+ def checkdigit?
119
+ @checkdigit
120
+ end
105
121
106
122
##
107
123
# total size of a given template character value
@@ -120,17 +136,17 @@ def character_space(c)
120
136
# @param [Integer] n
121
137
# @return [String]
122
138
def n2xdig ( n )
123
- xdig = characters . reverse . split ( '' ) . map do | c |
124
- value = n % character_space ( c )
125
- n /= character_space ( c )
139
+ xdig = size_list . reverse . map { | size |
140
+ value = n % size
141
+ n /= size
126
142
Noid ::XDIGIT [ value ]
127
- end . compact . join ( '' )
143
+ } . compact . join ( '' )
128
144
129
145
if generator == 'z'
130
- c = characters . split ( '' ) . last
146
+ size = size_list . last
131
147
while n > 0
132
- value = n % character_space ( c )
133
- n /= character_space ( c )
148
+ value = n % size
149
+ n /= size
134
150
xdig += Noid ::XDIGIT [ value ]
135
151
end
136
152
end
0 commit comments