Class: Barcode1DTools::MSI

Inherits:
Barcode1D show all
Defined in:
lib/barcode1dtools/msi.rb

Overview

Barcode1DTools::MSI - Create and decode bar patterns for MSI. The value encoded is a string which may contain the digits 0-9.

There are four possible check digit calculations, and you may use the option :check_digit => ‘x’ to choose which one to use. “x” may be one of “mod 10”, “mod 11”, “mod 1010”, or “mod 1110”. The default is “mod 10”. For a mod 11 check digit, you may use :check_style => ‘ibm’ or ‘ncr’.

MSI is a terrible symbology in modern terms and should not be used in any new applications.

Example

val = "2898289238"
bc = Barcode1DTools::MSI.new(val)
pattern = bc.bars
rle_pattern = bc.rle
width = bc.width

The object created is immutable.

Barcode1DTools::MSI creates the patterns that you need to display MSI barcodes. It can also decode a simple w/n string.

MSI characters consist of 4 bars and 4 spaces. The representation is simply binary where a binary “0” is represented as a narrow bar followed by a wide space and a binary “1” is a wide bar followed by a narrow space. The bits are ordered descending, so 9 is 1001 binary, “wn nw nw wn” in w/n format.

Formats

There are three formats for the returned pattern:

bars - 1s and 0s specifying black lines and white spaces. Actual characters can be changed from “1” and 0“ with options :line_character and :space_character.

rle - Run-length-encoded version of the pattern. The first number is always a black line, with subsequent digits alternating between spaces and lines. The digits specify the width of each line or space.

wn - The native format for this barcode type. The string consists of a series of “w” and “n” characters. The first item is always a black line, with subsequent characters alternating between spaces and lines. A “wide” item is twice the width of a “narrow” item.

The “width” method will tell you the total end-to-end width, in units, of the entire barcode.

Rendering

The author is aware of no standards for display.

Constant Summary collapse

CHAR_SEQUENCE =

Character sequence - 0-based offset in this string is character number

"0123456789"
PATTERNS =

Patterns for making bar codes

{
  '0'=> {'val'=>0 ,'wn'=>'nwnwnwnw'},
  '1'=> {'val'=>1 ,'wn'=>'nwnwnwwn'},
  '2'=> {'val'=>2 ,'wn'=>'nwnwwnnw'},
  '3'=> {'val'=>3 ,'wn'=>'nwnwwnwn'},
  '4'=> {'val'=>4 ,'wn'=>'nwwnnwnw'},
  '5'=> {'val'=>5 ,'wn'=>'nwwnnwwn'},
  '6'=> {'val'=>6 ,'wn'=>'nwwnwnnw'},
  '7'=> {'val'=>7 ,'wn'=>'nwwnwnwn'},
  '8'=> {'val'=>8 ,'wn'=>'wnnwnwnw'},
  '9'=> {'val'=>9 ,'wn'=>'wnnwnwwn'}
}
GUARD_PATTERN_LEFT_WN =

Left guard pattern

'wn'
GUARD_PATTERN_RIGHT_WN =

Right guard pattern

'nwn'
DEFAULT_OPTIONS =
{
  :line_character => '1',
  :space_character => '0',
  :w_character => 'w',
  :n_character => 'n',
  :wn_ratio => '2',
  :check_digit => 'mod 10',
  :check_style => 'ibm'
}

Instance Attribute Summary

Attributes inherited from Barcode1D

#check_digit, #encoded_string, #options, #value

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Barcode1D

bar_pair, bars_to_rle, rle_to_bars, rle_to_wn, wn_pair, wn_to_rle

Constructor Details

#initialize(value, options = {}) ⇒ MSI

Create a new MSI object with a given value. Options are :line_character, :space_character, :w_character, :n_character, :check_digit, :checksum_included, and :skip_checksum.



223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/barcode1dtools/msi.rb', line 223

def initialize(value, options = {})

  @options = DEFAULT_OPTIONS.merge(options)

  # Can we encode this value?
  raise UnencodableCharactersError unless self.class.can_encode?(value)

  if @options[:skip_checksum]
    @encoded_string = value.to_s
    @value = value.to_s
    @check_digit = nil
  elsif @options[:checksum_included]
    raise ChecksumError unless self.class.validate_check_digit_for(value, @options)
    @encoded_string = value.to_s
    @value, @check_digit = self.class.split_payload_and_check_digits(value, @options)
  else
    @value = value.to_s
    @check_digit = self.class.generate_check_digit_for(@value, @options)
    @encoded_string = "#{@value}#{@check_digit}"
  end
end

Class Method Details

.can_encode?(value) ⇒ Boolean

MSI can encode digits - returns true if given a string of digits.

Returns:

  • (Boolean)


106
107
108
# File 'lib/barcode1dtools/msi.rb', line 106

def can_encode?(value)
  value.to_s =~ /\A\d+\z/
end

.decode(str, options = {}) ⇒ Object

Decode a string in rle format. This will return a MSI object.



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/barcode1dtools/msi.rb', line 171

def decode(str, options = {})
  if str =~ /[^1-3]/ && str =~ /[^wn]/
    raise UnencodableCharactersError, "Pattern must be rle or wn"
  end

  # ensure a wn string
  if str =~ /[1-3]/
    str = str.tr('123','nww')
  end

  if str.reverse =~ /\A#{GUARD_PATTERN_LEFT_WN}.*?#{GUARD_PATTERN_RIGHT_WN}\z/
    str.reverse!
  end

  unless str =~ /\A#{GUARD_PATTERN_LEFT_WN}(.*?)#{GUARD_PATTERN_RIGHT_WN}\z/
    raise UnencodableCharactersError, "Start/stop pattern is not detected."
  end

  wn_pattern = $1

  # Each pattern is 4 bars and 4 spaces, with a space between.
  unless wn_pattern.size % 8 == 0
    raise UnencodableCharactersError, "Wrong number of bars."
  end

  decoded_string = ''

  wn_pattern.scan(/.{8}/).each do |chunk|

    found = false

    PATTERNS.each do |char,hsh|
      if chunk == hsh['wn']
        decoded_string += char
        found = true
        break;
      end
    end

    raise UndecodableCharactersError, "Invalid sequence: #{chunk}" unless found

  end

  MSI.new(decoded_string, options)
end

.generate_check_digit_for(value, options = {}) ⇒ Object

Generates a check digit using one of four algorithms. The algorithm must be specified in the second parameter (the options hash).



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/barcode1dtools/msi.rb', line 112

def generate_check_digit_for(value, options = {})
  if options[:check_digit] == 'mod 10'
    generate_mod10_check_digit_for(value).to_s
  elsif options[:check_digit] == 'mod 11'
    generate_mod11_check_digit_for(value, options[:check_style]).to_s
  elsif options[:check_digit] == 'mod 1010'
    mod10 = generate_mod10_check_digit_for(value)
    mod10_2 = generate_mod10_check_digit_for(value + mod10.to_s)
    "#{mod10}#{mod10_2}"
  elsif options[:check_digit] == 'mod 1110'
    mod11 = generate_mod11_check_digit_for(value, options[:check_style])
    mod10_2 = generate_mod10_check_digit_for(value + mod11.to_s)
    "#{mod11}#{mod10_2}"
  end
end

.generate_mod10_check_digit_for(value) ⇒ Object

Generates a mod 10 check digit.



145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/barcode1dtools/msi.rb', line 145

def generate_mod10_check_digit_for(value)
  value = value.to_s
  valarr = value.scan(/\d\d?/)
  if value.size.odd?
    odd = valarr.collect { |c| c[0,1] }
    even = valarr.collect { |c| c[1,1] }
  else
    odd = valarr.collect { |c| c[1,1] }
    even = valarr.collect { |c| c[0,1] }
  end
  odd = (odd.join.to_i * 2).to_s.split('').inject(0) { |a,c| a + c.to_i }
  even = even.inject(0) { |a,c| a + c.to_i }
  (10 - ((odd + even) % 10)) % 10
end

.generate_mod11_check_digit_for(value, style) ⇒ Object

Generates a mod 11 check digit.



161
162
163
164
165
166
167
# File 'lib/barcode1dtools/msi.rb', line 161

def generate_mod11_check_digit_for(value, style)
  max = (style == 'ncr' ? 9 : 7)
  value = value.to_s
  weight = 1
  sum = value.split('').reverse.inject(0) { |a,c| weight = (weight == max ? 2 : weight + 1); a + weight * c.to_i }
  (11 - (sum % 11)) % 11
end

.split_payload_and_check_digits(value, options = {}) ⇒ Object

Splits payload and check digit(s) given a check_digit option.



135
136
137
138
139
140
141
142
# File 'lib/barcode1dtools/msi.rb', line 135

def split_payload_and_check_digits(value, options = {})
  if options[:check_digit] == 'mod 1010' || options[:check_digit] == 'mod 1110'
    md = value.to_s.match(/\A(.*?)(..)\z/)
  else
    md = value.to_s.match(/\A(.*?)(.)\z/)
  end
  [md[1], md[2]]
end

.validate_check_digit_for(value, options = {}) ⇒ Object

Validates the check digit(s) for a given string.



129
130
131
132
# File 'lib/barcode1dtools/msi.rb', line 129

def validate_check_digit_for(value, options = {})
  payload, check_digits = split_payload_and_check_digits(value, options)
  self.generate_check_digit_for(payload, options) == check_digits
end

Instance Method Details

#barsObject

Returns 1s and 0s (for “black” and “white”)



256
257
258
# File 'lib/barcode1dtools/msi.rb', line 256

def bars
  @bars ||= self.class.rle_to_bars(self.rle, @options)
end

#rleObject

Returns a run-length-encoded string representation



251
252
253
# File 'lib/barcode1dtools/msi.rb', line 251

def rle
  @rle ||= self.class.wn_to_rle(self.wn, @options)
end

#widthObject

Returns the total unit width of the bar code



261
262
263
# File 'lib/barcode1dtools/msi.rb', line 261

def width
  @width ||= rle.split('').inject(0) { |a,c| a + c.to_i }
end

#wnObject

Returns a string of “w” or “n” (“wide” and “narrow”)



246
247
248
# File 'lib/barcode1dtools/msi.rb', line 246

def wn
  @wn ||= wn_str.tr('wn', @options[:w_character].to_s + @options[:n_character].to_s)
end