smstools 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +71 -0
- data/Rakefile +11 -0
- data/lib/assets/javascripts/sms_tools/index.js.coffee +1 -0
- data/lib/assets/javascripts/sms_tools/message.js.coffee +58 -0
- data/lib/sms_tools.rb +7 -0
- data/lib/sms_tools/encoding_detection.rb +62 -0
- data/lib/sms_tools/gsm_encoding.rb +195 -0
- data/lib/sms_tools/rails/engine.rb +6 -0
- data/lib/sms_tools/version.rb +3 -0
- data/lib/smstools.rb +1 -0
- data/smstools.gemspec +25 -0
- data/spec/sms_tools/encoding_detection_spec.rb +153 -0
- data/spec/spec_helper.rb +8 -0
- metadata +120 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4180ff46eb40f9d709cf9895d1dc493aeec4203c
|
4
|
+
data.tar.gz: cba4fd705516f2418dfae93ce3732231e30faba5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b3b26b73c00f2acbb9e107ecc24738b97a713a3579c685339226b9ba369c0d2080ff7b40357a472c0dc9ebe41bca0f8ed38e28cd132242ce7ed3e514a78d69c3
|
7
|
+
data.tar.gz: 9b7a0e6a64fdaec4f8f05130dc3b4ac735f2848fddfd1bbd8d274ec2b6e9781fcdb993ef121357e77f6b4c486508a067e6480516a72fd33d7c28007397f0fde5
|
data/.gitignore
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Dimitar Dimitrov
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# Sms Tools
|
2
|
+
|
3
|
+
A small collection of useful Ruby and JavaScript classes implementing often
|
4
|
+
needed functionality for dealing with SMS messages.
|
5
|
+
|
6
|
+
The gem is also a Rails engine and using it in your Rails app will allow you
|
7
|
+
to also use the JavaScript classes via the asset pipeline.
|
8
|
+
|
9
|
+
## Features
|
10
|
+
|
11
|
+
The following features are available on both the server side and the client
|
12
|
+
side:
|
13
|
+
|
14
|
+
- Detection of the most optimal encoding for sending an SMS message (GSM 7-bit
|
15
|
+
or Unicode).
|
16
|
+
- Correctly determining the message's length according to the most optimal
|
17
|
+
encoding.
|
18
|
+
- Concatenation detection and concatenated message parts counting.
|
19
|
+
|
20
|
+
And more.
|
21
|
+
|
22
|
+
## Installation
|
23
|
+
|
24
|
+
Add this line to your application's Gemfile:
|
25
|
+
|
26
|
+
gem 'smstools'
|
27
|
+
|
28
|
+
And then execute:
|
29
|
+
|
30
|
+
$ bundle
|
31
|
+
|
32
|
+
Or install it yourself as:
|
33
|
+
|
34
|
+
$ gem install smstools
|
35
|
+
|
36
|
+
## Usage
|
37
|
+
|
38
|
+
The gem consists of both server-side (Ruby) and client-side classes. You can
|
39
|
+
use either one.
|
40
|
+
|
41
|
+
### Server-side code
|
42
|
+
|
43
|
+
If you use the gem in Rails or via Bundler, just use the appropriate class,
|
44
|
+
such as `SmsTools::EncodingDetection` or `SmsTools::GsmEncoding`.
|
45
|
+
|
46
|
+
#### `EncodingDetection`
|
47
|
+
#### `GsmEncoding`
|
48
|
+
|
49
|
+
### Client-side code
|
50
|
+
|
51
|
+
If you're using the gem in Rails 3.x or newer, you can just add the following
|
52
|
+
to your `application.js` file to gain access to the JavaScript classes:
|
53
|
+
|
54
|
+
#= require 'sms_tools/all'
|
55
|
+
|
56
|
+
Or require only the files you need:
|
57
|
+
|
58
|
+
#= require 'sms_tools/message'
|
59
|
+
|
60
|
+
Note that this assumes you're using the asset pipeline. You need to have a
|
61
|
+
CoffeeScript preprocessor set up.
|
62
|
+
|
63
|
+
## Contributing
|
64
|
+
|
65
|
+
1. [Fork the repo](http://github.com/mitio/smstools/fork)
|
66
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
67
|
+
3. Make your changes and provide tests for them
|
68
|
+
4. Make sure all tests pass (run them with `bundle exec rake test`)
|
69
|
+
5. Commit your changes (`git commit -am 'Add some feature'`)
|
70
|
+
6. Push to the branch (`git push origin my-new-feature`)
|
71
|
+
7. Send a pull request.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
#= require_tree .
|
@@ -0,0 +1,58 @@
|
|
1
|
+
window.SmsTools ?= {}
|
2
|
+
|
3
|
+
class SmsTools.Message
|
4
|
+
maxLengthForEncoding:
|
5
|
+
gsm:
|
6
|
+
normal: 160
|
7
|
+
concatenated: 153
|
8
|
+
unicode:
|
9
|
+
normal: 70
|
10
|
+
concatenated: 67
|
11
|
+
|
12
|
+
doubleByteCharsInGsmEncoding:
|
13
|
+
'^': true
|
14
|
+
'{': true
|
15
|
+
'}': true
|
16
|
+
'[': true
|
17
|
+
'~': true
|
18
|
+
']': true
|
19
|
+
'|': true
|
20
|
+
'€': true
|
21
|
+
'\\': true
|
22
|
+
|
23
|
+
gsmEncodingPattern: /^[0-9a-zA-Z@Δ¡¿£_!Φ"¥Γ#èΛ¤éΩ%ùΠ&ìΨòΣçΘΞ:Ø;ÄäøÆ,<Ööæ=ÑñÅß>Üüåɧà€~ \$\.\-\+\(\)\*\\\/\?\|\^\}\{\[\]\'\r\n]*$/
|
24
|
+
|
25
|
+
constructor: (@text) ->
|
26
|
+
@text = @text.replace /\r\n?/g, "\n"
|
27
|
+
@encoding = @_encoding()
|
28
|
+
@length = @_length()
|
29
|
+
@concatenatedPartsCount = @_concatenatedPartsCount()
|
30
|
+
|
31
|
+
maxLengthFor: (concatenatedPartsCount) ->
|
32
|
+
messageType = if concatenatedPartsCount > 1 then 'concatenated' else 'normal'
|
33
|
+
|
34
|
+
concatenatedPartsCount * @maxLengthForEncoding[@encoding][messageType]
|
35
|
+
|
36
|
+
_encoding: ->
|
37
|
+
if @gsmEncodingPattern.test(@text) then 'gsm' else 'unicode'
|
38
|
+
|
39
|
+
_concatenatedPartsCount: ->
|
40
|
+
encoding = @encoding
|
41
|
+
length = @length
|
42
|
+
|
43
|
+
if length <= @maxLengthForEncoding[encoding].normal
|
44
|
+
1
|
45
|
+
else
|
46
|
+
parseInt Math.ceil(length / @maxLengthForEncoding[encoding].concatenated), 10
|
47
|
+
|
48
|
+
# Returns the number of symbols, which the given text will take up in an SMS
|
49
|
+
# message, taking into account any double-space symbols in the GSM 03.38
|
50
|
+
# encoding.
|
51
|
+
_length: ->
|
52
|
+
length = @text.length
|
53
|
+
|
54
|
+
if @encoding == 'gsm'
|
55
|
+
for char in @text
|
56
|
+
length += 1 if @doubleByteCharsInGsmEncoding[char]
|
57
|
+
|
58
|
+
length
|
data/lib/sms_tools.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'sms_tools/gsm_encoding'
|
2
|
+
|
3
|
+
module SmsTools
|
4
|
+
class EncodingDetection
|
5
|
+
MAX_LENGTH_FOR_ENCODING = {
|
6
|
+
gsm: {
|
7
|
+
normal: 160,
|
8
|
+
concatenated: 153,
|
9
|
+
},
|
10
|
+
unicode: {
|
11
|
+
normal: 70,
|
12
|
+
concatenated: 67,
|
13
|
+
},
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
attr :text
|
17
|
+
|
18
|
+
def initialize(text)
|
19
|
+
@text = text
|
20
|
+
end
|
21
|
+
|
22
|
+
def encoding
|
23
|
+
@encoding ||= GsmEncoding.valid?(text) ? :gsm : :unicode
|
24
|
+
end
|
25
|
+
|
26
|
+
def gsm?
|
27
|
+
encoding == :gsm
|
28
|
+
end
|
29
|
+
|
30
|
+
def unicode?
|
31
|
+
encoding == :unicode
|
32
|
+
end
|
33
|
+
|
34
|
+
def concatenated?
|
35
|
+
concatenated_parts > 1
|
36
|
+
end
|
37
|
+
|
38
|
+
def concatenated_parts
|
39
|
+
if length <= MAX_LENGTH_FOR_ENCODING[encoding][:normal]
|
40
|
+
1
|
41
|
+
else
|
42
|
+
(length.to_f / MAX_LENGTH_FOR_ENCODING[encoding][:concatenated]).ceil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def maximum_length_for(concatenated_parts)
|
47
|
+
message_type = concatenated_parts > 1 ? :concatenated : :normal
|
48
|
+
|
49
|
+
concatenated_parts * MAX_LENGTH_FOR_ENCODING[encoding][message_type]
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns the number of symbols, which the given text will take up in an SMS
|
53
|
+
# message, taking into account any double-space symbols in the GSM 03.38
|
54
|
+
# encoding.
|
55
|
+
def length
|
56
|
+
length = text.length
|
57
|
+
length += text.chars.count { |char| GsmEncoding.double_byte?(char) } if gsm?
|
58
|
+
|
59
|
+
length
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
module SmsTools
|
2
|
+
# UTF-8 to GSM-7 (GSM 03.38) mapping. Based on code from:
|
3
|
+
# https://github.com/threez/smspromote/blob/master/lib/smspromote/encoding.rb
|
4
|
+
module GsmEncoding
|
5
|
+
extend self
|
6
|
+
|
7
|
+
UTF8_TO_GSM_BASE_TABLE = {
|
8
|
+
0x0040 => "\x00", # COMMERCIAL AT
|
9
|
+
0x00A3 => "\x01", # POUND SIGN
|
10
|
+
0x0024 => "\x02", # DOLLAR SIGN
|
11
|
+
0x00A5 => "\x03", # YEN SIGN
|
12
|
+
0x00E8 => "\x04", # LATIN SMALL LETTER E WITH GRAVE
|
13
|
+
0x00E9 => "\x05", # LATIN SMALL LETTER E WITH ACUTE
|
14
|
+
0x00F9 => "\x06", # LATIN SMALL LETTER U WITH GRAVE
|
15
|
+
0x00EC => "\x07", # LATIN SMALL LETTER I WITH GRAVE
|
16
|
+
0x00F2 => "\x08", # LATIN SMALL LETTER O WITH GRAVE
|
17
|
+
0x00E7 => "\x09", # LATIN SMALL LETTER C WITH CEDILLA
|
18
|
+
0x000A => "\x0A", # LINE FEED
|
19
|
+
0x00D8 => "\x0B", # LATIN CAPITAL LETTER O WITH STROKE
|
20
|
+
0x00F8 => "\x0C", # LATIN SMALL LETTER O WITH STROKE
|
21
|
+
0x000D => "\x0D", # CARRIAGE RETURN
|
22
|
+
0x00C5 => "\x0E", # LATIN CAPITAL LETTER A WITH RING ABOVE
|
23
|
+
0x00E5 => "\x0F", # LATIN SMALL LETTER A WITH RING ABOVE
|
24
|
+
0x0394 => "\x10", # GREEK CAPITAL LETTER DELTA
|
25
|
+
0x005F => "\x11", # LOW LINE
|
26
|
+
0x03A6 => "\x12", # GREEK CAPITAL LETTER PHI
|
27
|
+
0x0393 => "\x13", # GREEK CAPITAL LETTER GAMMA
|
28
|
+
0x039B => "\x14", # GREEK CAPITAL LETTER LAMDA
|
29
|
+
0x03A9 => "\x15", # GREEK CAPITAL LETTER OMEGA
|
30
|
+
0x03A0 => "\x16", # GREEK CAPITAL LETTER PI
|
31
|
+
0x03A8 => "\x17", # GREEK CAPITAL LETTER PSI
|
32
|
+
0x03A3 => "\x18", # GREEK CAPITAL LETTER SIGMA
|
33
|
+
0x0398 => "\x19", # GREEK CAPITAL LETTER THETA
|
34
|
+
0x039E => "\x1A", # GREEK CAPITAL LETTER XI
|
35
|
+
0x00A0 => "\x1B", # ESCAPE TO EXTENSION TABLE
|
36
|
+
0x00C6 => "\x1C", # LATIN CAPITAL LETTER AE
|
37
|
+
0x00E6 => "\x1D", # LATIN SMALL LETTER AE
|
38
|
+
0x00DF => "\x1E", # LATIN SMALL LETTER SHARP S (German)
|
39
|
+
0x00C9 => "\x1F", # LATIN CAPITAL LETTER E WITH ACUTE
|
40
|
+
0x0020 => "\x20", # SPACE
|
41
|
+
0x0021 => "\x21", # EXCLAMATION MARK
|
42
|
+
0x0022 => "\x22", # QUOTATION MARK
|
43
|
+
0x0023 => "\x23", # NUMBER SIGN
|
44
|
+
0x00A4 => "\x24", # CURRENCY SIGN
|
45
|
+
0x0025 => "\x25", # PERCENT SIGN
|
46
|
+
0x0026 => "\x26", # AMPERSAND
|
47
|
+
0x0027 => "\x27", # APOSTROPHE
|
48
|
+
0x0028 => "\x28", # LEFT PARENTHESIS
|
49
|
+
0x0029 => "\x29", # RIGHT PARENTHESIS
|
50
|
+
0x002A => "\x2A", # ASTERISK
|
51
|
+
0x002B => "\x2B", # PLUS SIGN
|
52
|
+
0x002C => "\x2C", # COMMA
|
53
|
+
0x002D => "\x2D", # HYPHEN-MINUS
|
54
|
+
0x002E => "\x2E", # FULL STOP
|
55
|
+
0x002F => "\x2F", # SOLIDUS
|
56
|
+
0x0030 => "\x30", # DIGIT ZERO
|
57
|
+
0x0031 => "\x31", # DIGIT ONE
|
58
|
+
0x0032 => "\x32", # DIGIT TWO
|
59
|
+
0x0033 => "\x33", # DIGIT THREE
|
60
|
+
0x0034 => "\x34", # DIGIT FOUR
|
61
|
+
0x0035 => "\x35", # DIGIT FIVE
|
62
|
+
0x0036 => "\x36", # DIGIT SIX
|
63
|
+
0x0037 => "\x37", # DIGIT SEVEN
|
64
|
+
0x0038 => "\x38", # DIGIT EIGHT
|
65
|
+
0x0039 => "\x39", # DIGIT NINE
|
66
|
+
0x003A => "\x3A", # COLON
|
67
|
+
0x003B => "\x3B", # SEMICOLON
|
68
|
+
0x003C => "\x3C", # LESS-THAN SIGN
|
69
|
+
0x003D => "\x3D", # EQUALS SIGN
|
70
|
+
0x003E => "\x3E", # GREATER-THAN SIGN
|
71
|
+
0x003F => "\x3F", # QUESTION MARK
|
72
|
+
0x00A1 => "\x40", # INVERTED EXCLAMATION MARK
|
73
|
+
0x0041 => "\x41", # LATIN CAPITAL LETTER A
|
74
|
+
0x0042 => "\x42", # LATIN CAPITAL LETTER B
|
75
|
+
0x0043 => "\x43", # LATIN CAPITAL LETTER C
|
76
|
+
0x0044 => "\x44", # LATIN CAPITAL LETTER D
|
77
|
+
0x0045 => "\x45", # LATIN CAPITAL LETTER E
|
78
|
+
0x0046 => "\x46", # LATIN CAPITAL LETTER F
|
79
|
+
0x0047 => "\x47", # LATIN CAPITAL LETTER G
|
80
|
+
0x0048 => "\x48", # LATIN CAPITAL LETTER H
|
81
|
+
0x0049 => "\x49", # LATIN CAPITAL LETTER I
|
82
|
+
0x004A => "\x4A", # LATIN CAPITAL LETTER J
|
83
|
+
0x004B => "\x4B", # LATIN CAPITAL LETTER K
|
84
|
+
0x004C => "\x4C", # LATIN CAPITAL LETTER L
|
85
|
+
0x004D => "\x4D", # LATIN CAPITAL LETTER M
|
86
|
+
0x004E => "\x4E", # LATIN CAPITAL LETTER N
|
87
|
+
0x004F => "\x4F", # LATIN CAPITAL LETTER O
|
88
|
+
0x0050 => "\x50", # LATIN CAPITAL LETTER P
|
89
|
+
0x0051 => "\x51", # LATIN CAPITAL LETTER Q
|
90
|
+
0x0052 => "\x52", # LATIN CAPITAL LETTER R
|
91
|
+
0x0053 => "\x53", # LATIN CAPITAL LETTER S
|
92
|
+
0x0054 => "\x54", # LATIN CAPITAL LETTER T
|
93
|
+
0x0055 => "\x55", # LATIN CAPITAL LETTER U
|
94
|
+
0x0056 => "\x56", # LATIN CAPITAL LETTER V
|
95
|
+
0x0057 => "\x57", # LATIN CAPITAL LETTER W
|
96
|
+
0x0058 => "\x58", # LATIN CAPITAL LETTER X
|
97
|
+
0x0059 => "\x59", # LATIN CAPITAL LETTER Y
|
98
|
+
0x005A => "\x5A", # LATIN CAPITAL LETTER Z
|
99
|
+
0x00C4 => "\x5B", # LATIN CAPITAL LETTER A WITH DIAERESIS
|
100
|
+
0x00D6 => "\x5C", # LATIN CAPITAL LETTER O WITH DIAERESIS
|
101
|
+
0x00D1 => "\x5D", # LATIN CAPITAL LETTER N WITH TILDE
|
102
|
+
0x00DC => "\x5E", # LATIN CAPITAL LETTER U WITH DIAERESIS
|
103
|
+
0x00A7 => "\x5F", # SECTION SIGN
|
104
|
+
0x00BF => "\x60", # INVERTED QUESTION MARK
|
105
|
+
0x0061 => "\x61", # LATIN SMALL LETTER A
|
106
|
+
0x0062 => "\x62", # LATIN SMALL LETTER B
|
107
|
+
0x0063 => "\x63", # LATIN SMALL LETTER C
|
108
|
+
0x0064 => "\x64", # LATIN SMALL LETTER D
|
109
|
+
0x0065 => "\x65", # LATIN SMALL LETTER E
|
110
|
+
0x0066 => "\x66", # LATIN SMALL LETTER F
|
111
|
+
0x0067 => "\x67", # LATIN SMALL LETTER G
|
112
|
+
0x0068 => "\x68", # LATIN SMALL LETTER H
|
113
|
+
0x0069 => "\x69", # LATIN SMALL LETTER I
|
114
|
+
0x006A => "\x6A", # LATIN SMALL LETTER J
|
115
|
+
0x006B => "\x6B", # LATIN SMALL LETTER K
|
116
|
+
0x006C => "\x6C", # LATIN SMALL LETTER L
|
117
|
+
0x006D => "\x6D", # LATIN SMALL LETTER M
|
118
|
+
0x006E => "\x6E", # LATIN SMALL LETTER N
|
119
|
+
0x006F => "\x6F", # LATIN SMALL LETTER O
|
120
|
+
0x0070 => "\x70", # LATIN SMALL LETTER P
|
121
|
+
0x0071 => "\x71", # LATIN SMALL LETTER Q
|
122
|
+
0x0072 => "\x72", # LATIN SMALL LETTER R
|
123
|
+
0x0073 => "\x73", # LATIN SMALL LETTER S
|
124
|
+
0x0074 => "\x74", # LATIN SMALL LETTER T
|
125
|
+
0x0075 => "\x75", # LATIN SMALL LETTER U
|
126
|
+
0x0076 => "\x76", # LATIN SMALL LETTER V
|
127
|
+
0x0077 => "\x77", # LATIN SMALL LETTER W
|
128
|
+
0x0078 => "\x78", # LATIN SMALL LETTER X
|
129
|
+
0x0079 => "\x79", # LATIN SMALL LETTER Y
|
130
|
+
0x007A => "\x7A", # LATIN SMALL LETTER Z
|
131
|
+
0x00E4 => "\x7B", # LATIN SMALL LETTER A WITH DIAERESIS
|
132
|
+
0x00F6 => "\x7C", # LATIN SMALL LETTER O WITH DIAERESIS
|
133
|
+
0x00F1 => "\x7D", # LATIN SMALL LETTER N WITH TILDE
|
134
|
+
0x00FC => "\x7E", # LATIN SMALL LETTER U WITH DIAERESIS
|
135
|
+
0x00E0 => "\x7F", # LATIN SMALL LETTER A WITH GRAVE
|
136
|
+
}.freeze
|
137
|
+
|
138
|
+
UTF8_TO_GSM_EXTENSION_TABLE = {
|
139
|
+
0x000C => "\x1B\x0A", # FORM FEED
|
140
|
+
0x005E => "\x1B\x14", # CIRCUMFLEX ACCENT
|
141
|
+
0x007B => "\x1B\x28", # LEFT CURLY BRACKET
|
142
|
+
0x007D => "\x1B\x29", # RIGHT CURLY BRACKET
|
143
|
+
0x005C => "\x1B\x2F", # REVERSE SOLIDUS
|
144
|
+
0x005B => "\x1B\x3C", # LEFT SQUARE BRACKET
|
145
|
+
0x007E => "\x1B\x3D", # TILDE
|
146
|
+
0x005D => "\x1B\x3E", # RIGHT SQUARE BRACKET
|
147
|
+
0x007C => "\x1B\x40", # VERTICAL LINE
|
148
|
+
0x20AC => "\x1B\x65" # EURO SIGN
|
149
|
+
}.freeze
|
150
|
+
|
151
|
+
UTF8_TO_GSM = UTF8_TO_GSM_BASE_TABLE.merge(UTF8_TO_GSM_EXTENSION_TABLE).freeze
|
152
|
+
GSM_TO_UTF8 = UTF8_TO_GSM.invert.freeze
|
153
|
+
|
154
|
+
def valid?(utf8_encoded_string)
|
155
|
+
utf8_encoded_string.unpack('U*').all? { |char| UTF8_TO_GSM[char] }
|
156
|
+
end
|
157
|
+
|
158
|
+
def double_byte?(char)
|
159
|
+
UTF8_TO_GSM_EXTENSION_TABLE[char.unpack('U').first]
|
160
|
+
end
|
161
|
+
|
162
|
+
def from_utf8(utf8_encoded_string)
|
163
|
+
gsm_encoded_string = ''
|
164
|
+
|
165
|
+
utf8_encoded_string.unpack('U*').each do |char|
|
166
|
+
if converted = UTF8_TO_GSM[char]
|
167
|
+
gsm_encoded_string << converted
|
168
|
+
else
|
169
|
+
raise "Unsupported symbol in GSM-7 encoding: 0x#{char.to_s(16).upcase}"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
gsm_encoded_string
|
174
|
+
end
|
175
|
+
|
176
|
+
def to_utf8(gsm_encoded_string)
|
177
|
+
utf8_encoded_string = ''
|
178
|
+
escape = false
|
179
|
+
escape_code = "\e".freeze
|
180
|
+
|
181
|
+
gsm_encoded_string.each_char do |char|
|
182
|
+
if char == escape_code
|
183
|
+
escape = true
|
184
|
+
elsif escape
|
185
|
+
escape = false
|
186
|
+
utf8_encoded_string << [GSM_TO_UTF8[escape_code + char]].pack('U')
|
187
|
+
else
|
188
|
+
utf8_encoded_string << [GSM_TO_UTF8[char]].pack('U')
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
utf8_encoded_string
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
data/lib/smstools.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'sms_tools'
|
data/smstools.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'sms_tools/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'smstools'
|
8
|
+
spec.version = SmsTools::VERSION
|
9
|
+
spec.authors = ['Dimitar Dimitrov']
|
10
|
+
spec.email = ['me@ddimitrov.name']
|
11
|
+
spec.summary = 'Small library of classes for common SMS-related functionality.'
|
12
|
+
spec.description = 'Features SMS text encoding detection, length counting, concatenation detection and more. Can be used with or without Rails. Requires Ruby 1.9 or newer.'
|
13
|
+
spec.homepage = 'https://github.com/mitio/smstools'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_development_dependency 'bundler', '~> 1.5'
|
22
|
+
spec.add_development_dependency 'rake'
|
23
|
+
spec.add_development_dependency 'minitest'
|
24
|
+
spec.add_development_dependency 'minitest-ansi'
|
25
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'sms_tools/encoding_detection'
|
3
|
+
|
4
|
+
describe SmsTools::EncodingDetection do
|
5
|
+
it "exposes the original text as a method" do
|
6
|
+
detection_for('foo').text.must_equal 'foo'
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "encoding" do
|
10
|
+
it "defaults to GSM encoding for empty messages" do
|
11
|
+
detection_for('').encoding.must_equal :gsm
|
12
|
+
end
|
13
|
+
|
14
|
+
it "returns GSM as encoding for simple ASCII text" do
|
15
|
+
detection_for('foo bar baz').encoding.must_equal :gsm
|
16
|
+
end
|
17
|
+
|
18
|
+
it "returns GSM as encoding for special symbols defined in GSM 03.38" do
|
19
|
+
detection_for('09azAZ@Δ¡¿£_!Φ"¥Γ#èΛ¤éΩ%ùΠ&ìΨòΣçΘΞ:Ø;ÄäøÆ,<Ööæ=ÑñÅß>Üüåɧà€~').encoding.must_equal :gsm
|
20
|
+
end
|
21
|
+
|
22
|
+
it "returns GSM as encoding for puntucation and newline symbols" do
|
23
|
+
detection_for('Foo bar {} [baz]! Larodi $5. What else?').encoding.must_equal :gsm
|
24
|
+
detection_for("Spaces and newlines are GSM 03.38, too: \r\n").encoding.must_equal :gsm
|
25
|
+
end
|
26
|
+
|
27
|
+
it "returns Unicode when non-GSM Unicode symbols are used" do
|
28
|
+
detection_for('Foo bar лароди').encoding.must_equal :unicode
|
29
|
+
detection_for('∞').encoding.must_equal :unicode
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "message length" do
|
34
|
+
it "computes the length of trivial ANSI-only messages correctly" do
|
35
|
+
detection_for('').length.must_equal 0
|
36
|
+
detection_for('larodi').length.must_equal 6
|
37
|
+
detection_for('a' * 180).length.must_equal 180
|
38
|
+
end
|
39
|
+
|
40
|
+
it "computes the length of non-trivial GSM encoded messages correctly" do
|
41
|
+
detection_for('GSM: 09azAZ@Δ¡¿£_!Φ"¥Γ#èΛ¤éΩ%ùΠ&ìΨòΣçΘΞ:Ø;ÄäøÆ,<Ööæ=ÑñÅß>Üüåɧà').length.must_equal 63
|
42
|
+
end
|
43
|
+
|
44
|
+
it "correctly counts the length of whitespace-only messages" do
|
45
|
+
detection_for(' ').length.must_equal 5
|
46
|
+
detection_for("\r\n ").length.must_equal 4
|
47
|
+
end
|
48
|
+
|
49
|
+
it "correctly counts the length of whitespace chars in GSM-encoded messages" do
|
50
|
+
detection_for('ΞØ ').length.must_equal 7
|
51
|
+
detection_for("ΞØ\r\n ").length.must_equal 6
|
52
|
+
end
|
53
|
+
|
54
|
+
it "counts double-space chars for GSM encoding" do
|
55
|
+
detection_for('^{}[~]|€\\').length.must_equal 18
|
56
|
+
detection_for('Σ: €').length.must_equal 5
|
57
|
+
end
|
58
|
+
|
59
|
+
it "computes the length of Unicode messages correctly" do
|
60
|
+
detection_for('кирилица').length.must_equal 8
|
61
|
+
detection_for('Я!').length.must_equal 2
|
62
|
+
detection_for("Уникод: ΞØ\r\n ").length.must_equal 14
|
63
|
+
detection_for('Ю' * 200).length.must_equal 200
|
64
|
+
end
|
65
|
+
|
66
|
+
it "doesn't count double-space chars for Unicode encoding" do
|
67
|
+
detection_for('Уникод: ^{}[~]|€\\').length.must_equal 17
|
68
|
+
detection_for('Уникод: Σ: €').length.must_equal 12
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "concatenated message parts counting" do
|
73
|
+
def concatenated_parts_for(length: nil, encoding: nil, must_be: nil)
|
74
|
+
SmsTools::EncodingDetection.new('').stub :length, length do |detection|
|
75
|
+
detection.stub :encoding, encoding do |detection|
|
76
|
+
detection.concatenated_parts.must_equal must_be
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
it "counts parts for GSM-encoded messages" do
|
82
|
+
concatenated_parts_for length: 0, encoding: :gsm, must_be: 1
|
83
|
+
concatenated_parts_for length: 160, encoding: :gsm, must_be: 1
|
84
|
+
concatenated_parts_for length: 161, encoding: :gsm, must_be: 2
|
85
|
+
concatenated_parts_for length: 306, encoding: :gsm, must_be: 2
|
86
|
+
concatenated_parts_for length: 307, encoding: :gsm, must_be: 3
|
87
|
+
concatenated_parts_for length: 459, encoding: :gsm, must_be: 3
|
88
|
+
concatenated_parts_for length: 500, encoding: :gsm, must_be: 4
|
89
|
+
end
|
90
|
+
|
91
|
+
it "counts parts for Unicode messages" do
|
92
|
+
concatenated_parts_for length: 0, encoding: :unicode, must_be: 1
|
93
|
+
concatenated_parts_for length: 70, encoding: :unicode, must_be: 1
|
94
|
+
concatenated_parts_for length: 71, encoding: :unicode, must_be: 2
|
95
|
+
concatenated_parts_for length: 134, encoding: :unicode, must_be: 2
|
96
|
+
concatenated_parts_for length: 135, encoding: :unicode, must_be: 3
|
97
|
+
end
|
98
|
+
|
99
|
+
it "counts parts for actual GSM-encoded and Unicode messages" do
|
100
|
+
detection_for('').concatenated_parts.must_equal 1
|
101
|
+
detection_for('Я').concatenated_parts.must_equal 1
|
102
|
+
detection_for('Σ' * 160).concatenated_parts.must_equal 1
|
103
|
+
detection_for('Σ' * 159 + '~').concatenated_parts.must_equal 2
|
104
|
+
detection_for('Я' * 133 + '~').concatenated_parts.must_equal 2
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe "maximum length for particular number of concatenated messages" do
|
109
|
+
it "works for GSM-encoded messages" do
|
110
|
+
detection_for('x').maximum_length_for(1).must_equal 160
|
111
|
+
detection_for('x').maximum_length_for(2).must_equal 306
|
112
|
+
detection_for('x').maximum_length_for(3).must_equal 459
|
113
|
+
end
|
114
|
+
|
115
|
+
it "works for Unicode messages" do
|
116
|
+
detection_for('ю').maximum_length_for(1).must_equal 70
|
117
|
+
detection_for('ю').maximum_length_for(2).must_equal 134
|
118
|
+
detection_for('ю').maximum_length_for(3).must_equal 201
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe "predicates" do
|
123
|
+
it "returns true for gsm? and false for unicode? if the encoding is GSM" do
|
124
|
+
detection_for('').stub :encoding, :gsm do |detection|
|
125
|
+
detection.must_be :gsm?
|
126
|
+
detection.wont_be :unicode?
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
it "returns false for gsm? and true for unicode? if the encoding is Unicode" do
|
131
|
+
detection_for('').stub :encoding, :unicode do |detection|
|
132
|
+
detection.wont_be :gsm?
|
133
|
+
detection.must_be :unicode?
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
it "returns true for concatenated? if concatenated_parts > 1" do
|
138
|
+
detection_for('').stub :concatenated_parts, 7 do |detection|
|
139
|
+
detection.must_be :concatenated?
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
it "returns false for concatenated? if concatenated_parts is 1" do
|
144
|
+
detection_for('').stub :concatenated_parts, 1 do |detection|
|
145
|
+
detection.wont_be :concatenated?
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def detection_for(text)
|
151
|
+
SmsTools::EncodingDetection.new text
|
152
|
+
end
|
153
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: smstools
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dimitar Dimitrov
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-01-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest-ansi
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: Features SMS text encoding detection, length counting, concatenation
|
70
|
+
detection and more. Can be used with or without Rails. Requires Ruby 1.9 or newer.
|
71
|
+
email:
|
72
|
+
- me@ddimitrov.name
|
73
|
+
executables: []
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- ".gitignore"
|
78
|
+
- CHANGELOG.md
|
79
|
+
- Gemfile
|
80
|
+
- LICENSE.txt
|
81
|
+
- README.md
|
82
|
+
- Rakefile
|
83
|
+
- lib/assets/javascripts/sms_tools/index.js.coffee
|
84
|
+
- lib/assets/javascripts/sms_tools/message.js.coffee
|
85
|
+
- lib/sms_tools.rb
|
86
|
+
- lib/sms_tools/encoding_detection.rb
|
87
|
+
- lib/sms_tools/gsm_encoding.rb
|
88
|
+
- lib/sms_tools/rails/engine.rb
|
89
|
+
- lib/sms_tools/version.rb
|
90
|
+
- lib/smstools.rb
|
91
|
+
- smstools.gemspec
|
92
|
+
- spec/sms_tools/encoding_detection_spec.rb
|
93
|
+
- spec/spec_helper.rb
|
94
|
+
homepage: https://github.com/mitio/smstools
|
95
|
+
licenses:
|
96
|
+
- MIT
|
97
|
+
metadata: {}
|
98
|
+
post_install_message:
|
99
|
+
rdoc_options: []
|
100
|
+
require_paths:
|
101
|
+
- lib
|
102
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
requirements: []
|
113
|
+
rubyforge_project:
|
114
|
+
rubygems_version: 2.2.0
|
115
|
+
signing_key:
|
116
|
+
specification_version: 4
|
117
|
+
summary: Small library of classes for common SMS-related functionality.
|
118
|
+
test_files:
|
119
|
+
- spec/sms_tools/encoding_detection_spec.rb
|
120
|
+
- spec/spec_helper.rb
|