# class Mail::Multibyte::Chars

Chars enables you to work transparently with UTF-8 encoding in the Ruby String class without having extensive knowledge about the encoding. A Chars object accepts a string upon initialization and proxies String methods in an encoding safe manner. All the normal String methods are also implemented on the proxy.

String methods are proxied through the Chars object, and can be accessed through the `mb_chars` method. Methods which would normally return a String object now return a Chars object so methods can be chained.

    "The Perfect String  ".mb_chars.downcase.strip.normalize # => "the perfect string"

Chars objects are perfectly interchangeable with String objects as long as no explicit class checks are made. If certain methods do explicitly check the class, call `to_s` before you pass chars objects to them.

    bad.explicit_checking_method "T".mb_chars.downcase.to_s

The default Chars implementation assumes that the encoding of the string is UTF-8, if you want to handle different encodings you can write your own multibyte string handler and configure it through Mail::Multibyte.proxy_class.

    class CharsForUTF32
      def size
        @wrapped_string.size / 4
      end

      def self.accepts?(string)
        string.length % 4 == 0
      end
    end

    Mail::Multibyte.proxy_class = CharsForUTF32

### Attributes

to_s[R]
to_str[R]
wrapped_string[R]

### Public Class Methods

consumes?(string) click to toggle source

Returns `true` when the proxy class can handle the string. Returns `false` otherwise.

    # File lib/mail/multibyte/chars.rb, line 73
    def self.consumes?(string)
      # Unpack is a little bit faster than regular expressions.
      string.unpack('U*')
      true
    rescue ArgumentError
      false
    end

new(string) click to toggle source

Creates a new Chars instance by wrapping string.

    # File lib/mail/multibyte/chars.rb, line 40
    def initialize(string)
      @wrapped_string = string
      @wrapped_string.force_encoding(Encoding::UTF_8) unless @wrapped_string.frozen?
    end

wants?(string) click to toggle source

Returns `true` if the Chars class can and should act as a proxy for the string string. Returns `false` otherwise.

    # File lib/mail/multibyte/chars.rb, line 97
    def self.wants?(string)
      $KCODE == 'UTF8' && consumes?(string)
    end

### Public Instance Methods

+(other) click to toggle source

Returns a new Chars object containing the other object concatenated to the string.

Example:

    ('Café'.mb_chars + ' périferôl').to_s # => "Café périferôl"

    # File lib/mail/multibyte/chars.rb, line 105
    def +(other)
      chars(@wrapped_string + other)
    end

<=>(other) click to toggle source

Returns -1, 0, or 1, depending on whether the Chars object is to be sorted before, equal or after the object on the right side of the operation. It accepts any object that implements `to_s`:

    'é'.mb_chars <=> 'ü'.mb_chars # => -1

See `String#<=>` for more details.

    # File lib/mail/multibyte/chars.rb, line 90
    def <=>(other)
      @wrapped_string <=> other.to_s
    end

=~(other) click to toggle source

Like `String#=~` only it returns the character offset (in codepoints) instead of the byte offset.

Example:

    'Café périferôl'.mb_chars =~ /ô/ # => 12

    # File lib/mail/multibyte/chars.rb, line 113
    def =~(other)
      translate_offset(@wrapped_string =~ other)
    end

[](*args)
Alias for: slice
[]=(*args) click to toggle source

Like `String#[]=`, except instead of byte offsets you specify character offsets.

Example:

    s = "Müller"
    s.mb_chars[2] = "e" # Replace character with offset 2
    s
    # => "Müeler"

    s = "Müller"
    s.mb_chars[1, 2] = "ö" # Replace 2 characters at character offset 1
    s
    # => "Möler"

    # File lib/mail/multibyte/chars.rb, line 263
    def []=(*args)
      replace_by = args.pop
      # Indexed replace with regular expressions already works
      if args.first.is_a?(Regexp)
        @wrapped_string[*args] = replace_by
      else
        result = Unicode.u_unpack(@wrapped_string)
        if args[0].is_a?(Integer)
          raise IndexError, "index #{args[0]} out of string" if args[0] >= result.length
          min = args[0]
          max = args[1].nil? ? min : (min + args[1] - 1)
          range = Range.new(min, max)
          replace_by = [replace_by].pack('U') if replace_by.is_a?(Integer)
        elsif args.first.is_a?(Range)
          raise RangeError, "#{args[0]} out of range" if args[0].min >= result.length
          range = args[0]
        else
          needle = args[0].to_s
          min = index(needle)
          max = min + Unicode.u_unpack(needle).length - 1
          range = Range.new(min, max)
        end
        result[range] = Unicode.u_unpack(replace_by)
        @wrapped_string.replace(result.pack('U*'))
      end
    end

acts_like_string?() click to toggle source

Enable more predictable duck-typing on String-like classes. See Object#acts_like?.

    # File lib/mail/multibyte/chars.rb, line 68
    def acts_like_string?
      true
    end

capitalize() click to toggle source

Converts the first character to uppercase and the remainder to lowercase.

Example:

    'über'.mb_chars.capitalize.to_s # => "Über"

    # File lib/mail/multibyte/chars.rb, line 356
    def capitalize
      (slice(0) || chars('')).upcase + (slice(1..-1) || chars('')).downcase
    end

center(integer, padstr=' ') click to toggle source

Works just like `String#center`, only integer specifies characters instead of bytes.

Example:

    "¾ cup".mb_chars.center(8).to_s
    # => " ¾ cup  "

    "¾ cup".mb_chars.center(8, " ").to_s # Use non-breaking whitespace
    # => " ¾ cup  "

    # File lib/mail/multibyte/chars.rb, line 231
    def center(integer, padstr=' ')
      justify(integer, :center, padstr)
    end

compose() click to toggle source

Performs composition on all the characters.

Example:

    'é'.length                       # => 3
    'é'.mb_chars.compose.to_s.length # => 2

    # File lib/mail/multibyte/chars.rb, line 394
    def compose
      chars(Unicode.compose_codepoints(Unicode.u_unpack(@wrapped_string)).pack('U*'))
    end

decompose() click to toggle source

Performs canonical decomposition on all the characters.

Example:

    'é'.length                         # => 2
    'é'.mb_chars.decompose.to_s.length # => 3

    # File lib/mail/multibyte/chars.rb, line 385
    def decompose
      chars(Unicode.decompose_codepoints(:canonical, Unicode.u_unpack(@wrapped_string)).pack('U*'))
    end

downcase() click to toggle source

Convert characters in the string to lowercase.

Example:

    'VĚDA A VÝZKUM'.mb_chars.downcase.to_s # => "věda a výzkum"

    # File lib/mail/multibyte/chars.rb, line 348
    def downcase
      chars(Unicode.apply_mapping(@wrapped_string, :lowercase_mapping))
    end

g_length() click to toggle source

Returns the number of grapheme clusters in the string.

Example:

    'क्षि'.mb_chars.length   # => 4
    'क्षि'.mb_chars.g_length # => 3

    # File lib/mail/multibyte/chars.rb, line 403
    def g_length
      Unicode.g_unpack(@wrapped_string).length
    end

include?(other) click to toggle source

Returns `true` if contained string contains other. Returns `false` otherwise.

Example:

    'Café'.mb_chars.include?('é') # => true

    # File lib/mail/multibyte/chars.rb, line 137
    def include?(other)
      # We have to redefine this method because Enumerable defines it.
      @wrapped_string.include?(other)
    end

index(needle, offset=0) click to toggle source

Returns the position needle in the string, counting in codepoints. Returns `nil` if needle isn't found.

Example:

    'Café périferôl'.mb_chars.index('ô')   # => 12
    'Café périferôl'.mb_chars.index(/\w/u) # => 0

    # File lib/mail/multibyte/chars.rb, line 147
    def index(needle, offset=0)
      wrapped_offset = first(offset).wrapped_string.length
      index = @wrapped_string.index(needle, wrapped_offset)
      index ? (Unicode.u_unpack(@wrapped_string.slice(0...index)).size) : nil
    end

insert(offset, fragment) click to toggle source

Inserts the passed string at specified codepoint offsets.

Example:

    'Café'.mb_chars.insert(4, ' périferôl').to_s # => "Café périferôl"

    # File lib/mail/multibyte/chars.rb, line 121
    def insert(offset, fragment)
      unpacked = Unicode.u_unpack(@wrapped_string)
      unless offset > unpacked.length
        @wrapped_string.replace(
          Unicode.u_unpack(@wrapped_string).insert(offset, *Unicode.u_unpack(fragment)).pack('U*')
        )
      else
        raise IndexError, "index #{offset} out of string"
      end
      self
    end

limit(limit) click to toggle source

Limit the byte size of the string to a number of bytes without breaking characters. Usable when the storage for a string is limited for some reason.

Example:

    s = 'こんにちは'
    s.mb_chars.limit(7) # => "こに"

    # File lib/mail/multibyte/chars.rb, line 332
    def limit(limit)
      slice(0...translate_offset(limit))
    end

ljust(integer, padstr=' ') click to toggle source

Works just like `String#ljust`, only integer specifies characters instead of bytes.

Example:

    "¾ cup".mb_chars.rjust(8).to_s
    # => "¾ cup   "

    "¾ cup".mb_chars.rjust(8, " ").to_s # Use non-breaking whitespace
    # => "¾ cup   "

    # File lib/mail/multibyte/chars.rb, line 218
    def ljust(integer, padstr=' ')
      justify(integer, :left, padstr)
    end

lstrip() click to toggle source

Strips entire range of Unicode whitespace from the left of the string.

    # File lib/mail/multibyte/chars.rb, line 179
    def lstrip
      chars(@wrapped_string.gsub(Unicode::LEADERS_PAT, ''))
    end

method_missing(method, *args, &block) click to toggle source

Forward all undefined methods to the wrapped string.

    # File lib/mail/multibyte/chars.rb, line 51
    def method_missing(method, *args, &block)
      if method.to_s =~ /!$/
        @wrapped_string.__send__(method, *args, &block)
        self
      else
        result = @wrapped_string.__send__(method, *args, &block)
        result.kind_of?(String) ? chars(result) : result
      end
    end

normalize(form = nil) click to toggle source

Returns the KC normalization of the string by default. NFKC is considered the best normalization form for passing strings to databases and validations.

- `form` - The form you want to normalize in. Should be one of the following: `:c`, `:kc`, `:d`, or `:kd`. Default is Mail::Multibyte::Unicode#default_normalization_form

    # File lib/mail/multibyte/chars.rb, line 376
    def normalize(form = nil)
      chars(Unicode.normalize(@wrapped_string, form))
    end

ord() click to toggle source

Returns the codepoint of the first character in the string.

Example:

    'こんにちは'.mb_chars.ord # => 12371

    # File lib/mail/multibyte/chars.rb, line 192
    def ord
      Unicode.u_unpack(@wrapped_string)[0]
    end

respond_to?(method, include_private=false) click to toggle source

Returns `true` if obj responds to the given method. Private methods are included in the search only if the optional second parameter evaluates to `true`.

Calls superclass method

    # File lib/mail/multibyte/chars.rb, line 63
    def respond_to?(method, include_private=false)
      super || @wrapped_string.respond_to?(method, include_private) || false
    end

reverse() click to toggle source

Reverses all characters in the string.

Example:

    'Café'.mb_chars.reverse.to_s # => 'éfaC'

    # File lib/mail/multibyte/chars.rb, line 294
    def reverse
      chars(Unicode.g_unpack(@wrapped_string).reverse.flatten.pack('U*'))
    end

rindex(needle, offset=nil) click to toggle source

Returns the position needle in the string, counting in codepoints, searching backward from offset or the end of the string. Returns `nil` if needle isn't found.

Example:

    'Café périferôl'.mb_chars.rindex('é')   # => 6
    'Café périferôl'.mb_chars.rindex(/\w/u) # => 13

    # File lib/mail/multibyte/chars.rb, line 160
    def rindex(needle, offset=nil)
      offset ||= length
      wrapped_offset = first(offset).wrapped_string.length
      index = @wrapped_string.rindex(needle, wrapped_offset)
      index ? (Unicode.u_unpack(@wrapped_string.slice(0...index)).size) : nil
    end

rjust(integer, padstr=' ') click to toggle source

Works just like `String#rjust`, only integer specifies characters instead of bytes.

Example:

    "¾ cup".mb_chars.rjust(8).to_s
    # => "   ¾ cup"

    "¾ cup".mb_chars.rjust(8, " ").to_s # Use non-breaking whitespace
    # => "   ¾ cup"

    # File lib/mail/multibyte/chars.rb, line 205
    def rjust(integer, padstr=' ')
      justify(integer, :right, padstr)
    end

rstrip() click to toggle source

Strips entire range of Unicode whitespace from the right of the string.

    # File lib/mail/multibyte/chars.rb, line 174
    def rstrip
      chars(@wrapped_string.gsub(Unicode::TRAILERS_PAT, ''))
    end

size() click to toggle source

Returns the number of codepoints in the string

    # File lib/mail/multibyte/chars.rb, line 168
    def size
      Unicode.u_unpack(@wrapped_string).size
    end

slice(*args) click to toggle source

Implements Unicode-aware slice with codepoints. Slicing on one point returns the codepoints for that character.

Example:

    'こんにちは'.mb_chars.slice(2..3).to_s # => "にち"

    # File lib/mail/multibyte/chars.rb, line 303
    def slice(*args)
      if args.size > 2
        raise ArgumentError, "wrong number of arguments (#{args.size} for 1)" # Do as if we were native
      elsif (args.size == 2 && !(args.first.is_a?(Numeric) || args.first.is_a?(Regexp)))
        raise TypeError, "cannot convert #{args.first.class} into Integer" # Do as if we were native
      elsif (args.size == 2 && !args[1].is_a?(Numeric))
        raise TypeError, "cannot convert #{args[1].class} into Integer" # Do as if we were native
      elsif args[0].kind_of? Range
        cps = Unicode.u_unpack(@wrapped_string).slice(*args)
        result = cps.nil? ? nil : cps.pack('U*')
      elsif args[0].kind_of? Regexp
        result = @wrapped_string.slice(*args)
      elsif args.size == 1 && args[0].kind_of?(Numeric)
        character = Unicode.u_unpack(@wrapped_string)[args[0]]
        result = character && [character].pack('U')
      else
        cps = Unicode.u_unpack(@wrapped_string).slice(*args)
        result = cps && cps.pack('U*')
      end
      result && chars(result)
    end

Also aliased as: []
split(*args) click to toggle source

Works just like `String#split`, with the exception that the items in the resulting list are Chars instances instead of String. This makes chaining methods easier.

Example:

    'Café périferôl'.mb_chars.split(/é/).map { |part| part.upcase.to_s } # => ["CAF", " P", "RIFERÔL"]

    # File lib/mail/multibyte/chars.rb, line 246
    def split(*args)
      @wrapped_string.split(*args).map { |i| i.mb_chars }
    end

strip() click to toggle source

Strips entire range of Unicode whitespace from the right and left of the string.

    # File lib/mail/multibyte/chars.rb, line 184
    def strip
      rstrip.lstrip
    end

tidy_bytes(force = false) click to toggle source

Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent resulting in a valid UTF-8 string.

Passing `true` will forcibly tidy all bytes, assuming that the string's encoding is entirely CP1252 or ISO-8859-1.

    # File lib/mail/multibyte/chars.rb, line 410
    def tidy_bytes(force = false)
      chars(Unicode.tidy_bytes(@wrapped_string, force))
    end

titlecase()
Alias for: titleize
titleize() click to toggle source

Capitalizes the first letter of every word, when possible.

Example:

    "ÉL QUE SE ENTERÓ".mb_chars.titleize    # => "Él Que Se Enteró"
    "日本語".mb_chars.titleize                 # => "日本語"

    # File lib/mail/multibyte/chars.rb, line 365
    def titleize
      chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.apply_mapping $1, :uppercase_mapping })
    end

Also aliased as: titlecase
upcase() click to toggle source

Convert characters in the string to uppercase.

Example:

    'Laurent, où sont les tests ?'.mb_chars.upcase.to_s # => "LAURENT, OÙ SONT LES TESTS ?"

    # File lib/mail/multibyte/chars.rb, line 340
    def upcase
      chars(Unicode.apply_mapping(@wrapped_string, :uppercase_mapping))
    end