nanaimo 0.1.0
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 +7 -0
- data/.gitignore +8 -0
- data/.rspec +2 -0
- data/.rubocop.yml +13 -0
- data/.rubocop_todo.yml +7 -0
- data/.travis.yml +13 -0
- data/CHANGELOG.md +8 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +49 -0
- data/LICENSE.txt +21 -0
- data/README.md +55 -0
- data/Rakefile +72 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/nanaimo.rb +21 -0
- data/lib/nanaimo/object.rb +100 -0
- data/lib/nanaimo/plist.rb +33 -0
- data/lib/nanaimo/reader.rb +252 -0
- data/lib/nanaimo/unicode.rb +88 -0
- data/lib/nanaimo/unicode/next_step_mapping.rb +136 -0
- data/lib/nanaimo/unicode/quote_maps.rb +56 -0
- data/lib/nanaimo/version.rb +3 -0
- data/lib/nanaimo/writer.rb +174 -0
- data/lib/nanaimo/writer/xml.rb +131 -0
- data/lib/nanaimo/xcode_project_writer.rb +76 -0
- data/nanaimo.gemspec +24 -0
- metadata +114 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
module Nanaimo
|
2
|
+
# A Plist.
|
3
|
+
#
|
4
|
+
class Plist
|
5
|
+
# @return [Nanaimo::Object] The root level object in the plist.
|
6
|
+
#
|
7
|
+
attr_accessor :root_object
|
8
|
+
|
9
|
+
# @return [String] The encoding of the plist.
|
10
|
+
#
|
11
|
+
attr_accessor :file_type
|
12
|
+
|
13
|
+
def initialize(root_object = nil, file_type = nil)
|
14
|
+
@root_object = root_object
|
15
|
+
@file_type = file_type
|
16
|
+
end
|
17
|
+
|
18
|
+
def ==(other)
|
19
|
+
return unless other.is_a?(Nanaimo::Plist)
|
20
|
+
file_type == other.file_type && root_object == other.root_object
|
21
|
+
end
|
22
|
+
|
23
|
+
def hash
|
24
|
+
root_object.hash
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return A native Ruby object representation of the plist.
|
28
|
+
#
|
29
|
+
def as_ruby
|
30
|
+
root_object.as_ruby
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,252 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
module Nanaimo
|
3
|
+
# Transforms plist strings into Plist objects.
|
4
|
+
#
|
5
|
+
class Reader
|
6
|
+
# Raised when attempting to read a plist with an unsupported file format.
|
7
|
+
#
|
8
|
+
class UnsupportedPlistFormatError < Error
|
9
|
+
# @return [Symbol] The unsupported format.
|
10
|
+
#
|
11
|
+
attr_reader :format
|
12
|
+
|
13
|
+
def initialize(format)
|
14
|
+
@format = format
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
"#{format} plists are currently unsupported"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Raised when parsing fails.
|
23
|
+
#
|
24
|
+
class ParseError < Error
|
25
|
+
# @return [[Integer, Integer]] The (line, column) offset into the plist
|
26
|
+
# where the error occurred
|
27
|
+
#
|
28
|
+
attr_accessor :location
|
29
|
+
|
30
|
+
# @return [String] The contents of the plist.
|
31
|
+
#
|
32
|
+
attr_accessor :plist_string
|
33
|
+
end
|
34
|
+
|
35
|
+
# @param plist_contents [String]
|
36
|
+
#
|
37
|
+
# @return [Symbol] The file format of the plist in the given string.
|
38
|
+
#
|
39
|
+
def self.plist_type(plist_contents)
|
40
|
+
case plist_contents
|
41
|
+
when /\Abplist/
|
42
|
+
:binary
|
43
|
+
when /\A<\?xml/
|
44
|
+
:xml
|
45
|
+
else
|
46
|
+
:ascii
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# @param file_path [String]
|
51
|
+
#
|
52
|
+
# @return [Plist] A parsed plist from the given file
|
53
|
+
#
|
54
|
+
def self.from_file(file_path)
|
55
|
+
new(File.read(file_path))
|
56
|
+
end
|
57
|
+
|
58
|
+
# @param contents [String] The plist to be parsed
|
59
|
+
#
|
60
|
+
def initialize(contents)
|
61
|
+
@scanner = StringScanner.new(contents)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Parses the contents of the plist
|
65
|
+
#
|
66
|
+
# @return [Plist] The parsed Plist object.
|
67
|
+
#
|
68
|
+
def parse!
|
69
|
+
plist_format = ensure_ascii_plist!
|
70
|
+
read_string_encoding
|
71
|
+
root_object = parse_object
|
72
|
+
|
73
|
+
eat_whitespace!
|
74
|
+
raise_parser_error ParseError, "unrecognized characters #{@scanner.rest.inspect} after parsing" unless @scanner.eos?
|
75
|
+
|
76
|
+
Nanaimo::Plist.new(root_object, plist_format)
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def ensure_ascii_plist!
|
82
|
+
self.class.plist_type(@scanner.string).tap do |plist_format|
|
83
|
+
raise UnsupportedPlistFormatError, plist_format unless plist_format == :ascii
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def read_string_encoding
|
88
|
+
# TODO
|
89
|
+
end
|
90
|
+
|
91
|
+
def parse_object
|
92
|
+
_comment = skip_to_non_space_matching_annotations
|
93
|
+
start_pos = @scanner.pos
|
94
|
+
raise_parser_error ParseError, 'Unexpected eos while parsing' if @scanner.eos?
|
95
|
+
if @scanner.skip(/\{/)
|
96
|
+
parse_dictionary
|
97
|
+
elsif @scanner.skip(/\(/)
|
98
|
+
parse_array
|
99
|
+
elsif @scanner.skip(/</)
|
100
|
+
parse_data
|
101
|
+
elsif quote = @scanner.scan(/['"]/)
|
102
|
+
parse_quotedstring(quote)
|
103
|
+
else
|
104
|
+
parse_string
|
105
|
+
end.tap do |o|
|
106
|
+
o.annotation = skip_to_non_space_matching_annotations
|
107
|
+
Nanaimo.debug { "parsed #{o.inspect} from #{start_pos}..#{@scanner.pos}" }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def parse_string
|
112
|
+
eat_whitespace!
|
113
|
+
unless match = @scanner.scan(%r{[\w/.]+})
|
114
|
+
raise_parser_error ParseError, "not a valid string at index #{@scanner.pos} (char is #{current_character.inspect})"
|
115
|
+
end
|
116
|
+
Nanaimo::String.new(match, nil)
|
117
|
+
end
|
118
|
+
|
119
|
+
def parse_quotedstring(quote)
|
120
|
+
unless string = @scanner.scan(/(?:([^#{quote}\\]|\\.)*)#{quote}/)
|
121
|
+
raise_parser_error ParseError, "unterminated quoted string started at #{@scanner.pos}, expected #{quote} but never found it"
|
122
|
+
end
|
123
|
+
string = Unicode.unquotify_string(string.chomp!(quote))
|
124
|
+
Nanaimo::QuotedString.new(string, nil)
|
125
|
+
end
|
126
|
+
|
127
|
+
def parse_array
|
128
|
+
objects = []
|
129
|
+
until @scanner.eos?
|
130
|
+
eat_whitespace!
|
131
|
+
break if @scanner.skip(/\)/)
|
132
|
+
|
133
|
+
objects << parse_object
|
134
|
+
|
135
|
+
eat_whitespace!
|
136
|
+
break if @scanner.skip(/\)/)
|
137
|
+
unless @scanner.skip(/,/)
|
138
|
+
raise_parser_error ParseError, "Array #{objects} missing ',' in between objects"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
Nanaimo::Array.new(objects, nil)
|
143
|
+
end
|
144
|
+
|
145
|
+
def parse_dictionary
|
146
|
+
objects = {}
|
147
|
+
until @scanner.eos?
|
148
|
+
skip_to_non_space_matching_annotations
|
149
|
+
break if @scanner.skip(/}/)
|
150
|
+
|
151
|
+
key = parse_object
|
152
|
+
eat_whitespace!
|
153
|
+
unless @scanner.skip(/=/)
|
154
|
+
raise_parser_error ParseError, "Dictionary missing value after key #{key.inspect} at index #{@scanner.pos}, expected '=' and got #{current_character.inspect}"
|
155
|
+
end
|
156
|
+
|
157
|
+
value = parse_object
|
158
|
+
objects[key] = value
|
159
|
+
|
160
|
+
eat_whitespace!
|
161
|
+
break if @scanner.skip(/}/)
|
162
|
+
unless @scanner.skip(/;/)
|
163
|
+
raise_parser_error ParseError, "Dictionary (#{objects}) missing ';' after key-value pair (#{key} = #{value}) at index #{@scanner.pos} (got #{current_character})"
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
Nanaimo::Dictionary.new(objects, nil)
|
168
|
+
end
|
169
|
+
|
170
|
+
def parse_data
|
171
|
+
unless data = @scanner.scan(/[\h ]*>/)
|
172
|
+
raise_parser_error ParseError, "Data missing closing '>'"
|
173
|
+
end
|
174
|
+
data.chomp!('>')
|
175
|
+
data.delete!(' ')
|
176
|
+
unless data.size.even?
|
177
|
+
@scanner.unscan
|
178
|
+
raise_parser_error ParseError, 'Data has an uneven number of hex digits'
|
179
|
+
end
|
180
|
+
data = [data].pack('H*')
|
181
|
+
Nanaimo::Data.new(data, nil)
|
182
|
+
end
|
183
|
+
|
184
|
+
def current_character
|
185
|
+
@scanner.peek(1)
|
186
|
+
end
|
187
|
+
|
188
|
+
def read_singleline_comment
|
189
|
+
unless comment = @scanner.scan_until(NEWLINE)
|
190
|
+
raise_parser_error ParseError, "failed to terminate single line comment #{@scanner.rest.inspect}"
|
191
|
+
end
|
192
|
+
comment
|
193
|
+
end
|
194
|
+
|
195
|
+
def eat_whitespace!
|
196
|
+
@scanner.skip(MANY_WHITESPACES)
|
197
|
+
end
|
198
|
+
|
199
|
+
NEWLINE_CHARACTERS = %W(\x0A \x0D \u2028 \u2029).freeze
|
200
|
+
NEWLINE = Regexp.union(*NEWLINE_CHARACTERS)
|
201
|
+
|
202
|
+
WHITESPACE_CHARACTERS = NEWLINE_CHARACTERS + %W(\x09 \x0B \x0C \x20)
|
203
|
+
WHITESPACE = Regexp.union(*WHITESPACE_CHARACTERS)
|
204
|
+
|
205
|
+
MANY_WHITESPACES = /#{WHITESPACE}+/
|
206
|
+
|
207
|
+
def read_multiline_comment
|
208
|
+
unless annotation = @scanner.scan(%r{(?:.+?)(?=\*/)}m)
|
209
|
+
raise_parser_error ParseError, "#{@scanner.rest.inspect} failed to terminate multiline comment"
|
210
|
+
end
|
211
|
+
@scanner.skip(%r{\*/})
|
212
|
+
|
213
|
+
annotation
|
214
|
+
end
|
215
|
+
|
216
|
+
def skip_to_non_space_matching_annotations
|
217
|
+
annotation = ''.freeze
|
218
|
+
until @scanner.eos?
|
219
|
+
eat_whitespace!
|
220
|
+
|
221
|
+
# Comment Detection
|
222
|
+
if @scanner.skip(%r{//})
|
223
|
+
annotation = read_singleline_comment
|
224
|
+
next
|
225
|
+
elsif @scanner.skip(%r{/\*})
|
226
|
+
annotation = read_multiline_comment
|
227
|
+
next
|
228
|
+
end
|
229
|
+
|
230
|
+
eat_whitespace!
|
231
|
+
|
232
|
+
break
|
233
|
+
end
|
234
|
+
annotation
|
235
|
+
end
|
236
|
+
|
237
|
+
def location_in(scanner)
|
238
|
+
pos = scanner.charpos
|
239
|
+
line = scanner.string[0..scanner.charpos].scan(NEWLINE).size + 1
|
240
|
+
column = pos - (scanner.string.rindex(NEWLINE, pos - 1) || -1)
|
241
|
+
[line, column]
|
242
|
+
end
|
243
|
+
|
244
|
+
def raise_parser_error(klass, message)
|
245
|
+
exception = klass.new(message).tap do |error|
|
246
|
+
error.location = location_in(@scanner)
|
247
|
+
error.plist_string = @scanner.string
|
248
|
+
end
|
249
|
+
raise(exception)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
require 'nanaimo/unicode/next_step_mapping'
|
3
|
+
require 'nanaimo/unicode/quote_maps'
|
4
|
+
module Nanaimo
|
5
|
+
# @!visibility private
|
6
|
+
#
|
7
|
+
module Unicode
|
8
|
+
class UnsupportedEscapeSequenceError < Error; end
|
9
|
+
class InvalidEscapeSequenceError < Error; end
|
10
|
+
|
11
|
+
module_function
|
12
|
+
|
13
|
+
def quotify_string(string)
|
14
|
+
string.gsub(QUOTE_REGEXP) { |s| QUOTE_MAP[s] }
|
15
|
+
end
|
16
|
+
|
17
|
+
ESCAPE_PREFIXES = %W(
|
18
|
+
0 1 2 3 4 5 6 7 a b f n r t v \n U
|
19
|
+
).freeze
|
20
|
+
|
21
|
+
OCTAL_DIGITS = (0..7).map(&:to_s).freeze
|
22
|
+
|
23
|
+
# Credit to Samantha Marshall
|
24
|
+
# Taken from https://github.com/samdmarshall/pbPlist/blob/346c29f91f913d35d0e24f6722ec19edb24e5707/pbPlist/StrParse.py#L197
|
25
|
+
# Licensed under https://raw.githubusercontent.com/samdmarshall/pbPlist/blob/346c29f91f913d35d0e24f6722ec19edb24e5707/LICENSE
|
26
|
+
#
|
27
|
+
# Originally from: http://www.opensource.apple.com/source/CF/CF-744.19/CFOldStylePList.c See `getSlashedChar()`
|
28
|
+
def unquotify_string(string)
|
29
|
+
formatted_string = ::String.new
|
30
|
+
extracted_string = string
|
31
|
+
string_length = string.size
|
32
|
+
index = 0
|
33
|
+
while index < string_length
|
34
|
+
if escape_index = extracted_string.index('\\', index)
|
35
|
+
formatted_string << extracted_string[index..escape_index - 1] unless index == escape_index
|
36
|
+
index = escape_index + 1
|
37
|
+
next_char = extracted_string[index]
|
38
|
+
if ESCAPE_PREFIXES.include?(next_char)
|
39
|
+
index += 1
|
40
|
+
if unquoted = UNQUOTE_MAP[next_char]
|
41
|
+
formatted_string << unquoted
|
42
|
+
elsif next_char == 'U'
|
43
|
+
length = 4
|
44
|
+
unicode_numbers = extracted_string[index, length]
|
45
|
+
unless unicode_numbers =~ /\A\h{4}\z/
|
46
|
+
raise InvalidEscapeSequenceError, "Unicode '\\U' escape sequence terminated without 4 following hex characters"
|
47
|
+
end
|
48
|
+
index += length
|
49
|
+
formatted_string << [unicode_numbers.to_i(16)].pack('U')
|
50
|
+
elsif OCTAL_DIGITS.include?(next_char) # https://twitter.com/Catfish_Man/status/658014170055507968
|
51
|
+
octal_string = extracted_string[index - 1, 3]
|
52
|
+
if octal_string =~ /\A[0-7]{3}\z/
|
53
|
+
index += 2
|
54
|
+
code_point = octal_string.to_i(8)
|
55
|
+
unless code_point <= 0x80 || converted = NEXT_STEP_MAPPING[code_point]
|
56
|
+
raise InvalidEscapeSequenceError, "Invalid octal escape sequence #{octal_string}"
|
57
|
+
end
|
58
|
+
formatted_string << [converted].pack('U')
|
59
|
+
else
|
60
|
+
formatted_string << next_char
|
61
|
+
end
|
62
|
+
else
|
63
|
+
raise UnsupportedEscapeSequenceError, "Failed to handle #{next_char} which is in the list of possible escapes"
|
64
|
+
end
|
65
|
+
else
|
66
|
+
index += 1
|
67
|
+
formatted_string << next_char
|
68
|
+
end
|
69
|
+
else
|
70
|
+
formatted_string << extracted_string[index..-1]
|
71
|
+
index = string_length
|
72
|
+
end
|
73
|
+
end
|
74
|
+
formatted_string
|
75
|
+
end
|
76
|
+
|
77
|
+
XML_STRING_ESCAPES = {
|
78
|
+
'&' => '&',
|
79
|
+
'<' => '<',
|
80
|
+
'>' => '>'
|
81
|
+
}.freeze
|
82
|
+
XML_STRING_ESCAPE_REGEXP = Regexp.union(XML_STRING_ESCAPES.keys)
|
83
|
+
|
84
|
+
def xml_escape_string(string)
|
85
|
+
string.to_s.gsub(XML_STRING_ESCAPE_REGEXP) { |m| XML_STRING_ESCAPES[m] }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
module Nanaimo
|
3
|
+
module Unicode
|
4
|
+
# Taken from http://ftp.unicode.org/Public/MAPPINGS/VENDORS/NEXT/NEXTSTEP.TXT
|
5
|
+
NEXT_STEP_MAPPING = {
|
6
|
+
0x80 => 0x00a0, # NO-BREAK SPACE
|
7
|
+
0x81 => 0x00c0, # LATIN CAPITAL LETTER A WITH GRAVE
|
8
|
+
0x82 => 0x00c1, # LATIN CAPITAL LETTER A WITH ACUTE
|
9
|
+
0x83 => 0x00c2, # LATIN CAPITAL LETTER A WITH CIRCUMFLEX
|
10
|
+
0x84 => 0x00c3, # LATIN CAPITAL LETTER A WITH TILDE
|
11
|
+
0x85 => 0x00c4, # LATIN CAPITAL LETTER A WITH DIAERESIS
|
12
|
+
0x86 => 0x00c5, # LATIN CAPITAL LETTER A WITH RING
|
13
|
+
0x87 => 0x00c7, # LATIN CAPITAL LETTER C WITH CEDILLA
|
14
|
+
0x88 => 0x00c8, # LATIN CAPITAL LETTER E WITH GRAVE
|
15
|
+
0x89 => 0x00c9, # LATIN CAPITAL LETTER E WITH ACUTE
|
16
|
+
0x8a => 0x00ca, # LATIN CAPITAL LETTER E WITH CIRCUMFLEX
|
17
|
+
0x8b => 0x00cb, # LATIN CAPITAL LETTER E WITH DIAERESIS
|
18
|
+
0x8c => 0x00cc, # LATIN CAPITAL LETTER I WITH GRAVE
|
19
|
+
0x8d => 0x00cd, # LATIN CAPITAL LETTER I WITH ACUTE
|
20
|
+
0x8e => 0x00ce, # LATIN CAPITAL LETTER I WITH CIRCUMFLEX
|
21
|
+
0x8f => 0x00cf, # LATIN CAPITAL LETTER I WITH DIAERESIS
|
22
|
+
0x90 => 0x00d0, # LATIN CAPITAL LETTER ETH
|
23
|
+
0x91 => 0x00d1, # LATIN CAPITAL LETTER N WITH TILDE
|
24
|
+
0x92 => 0x00d2, # LATIN CAPITAL LETTER O WITH GRAVE
|
25
|
+
0x93 => 0x00d3, # LATIN CAPITAL LETTER O WITH ACUTE
|
26
|
+
0x94 => 0x00d4, # LATIN CAPITAL LETTER O WITH CIRCUMFLEX
|
27
|
+
0x95 => 0x00d5, # LATIN CAPITAL LETTER O WITH TILDE
|
28
|
+
0x96 => 0x00d6, # LATIN CAPITAL LETTER O WITH DIAERESIS
|
29
|
+
0x97 => 0x00d9, # LATIN CAPITAL LETTER U WITH GRAVE
|
30
|
+
0x98 => 0x00da, # LATIN CAPITAL LETTER U WITH ACUTE
|
31
|
+
0x99 => 0x00db, # LATIN CAPITAL LETTER U WITH CIRCUMFLEX
|
32
|
+
0x9a => 0x00dc, # LATIN CAPITAL LETTER U WITH DIAERESIS
|
33
|
+
0x9b => 0x00dd, # LATIN CAPITAL LETTER Y WITH ACUTE
|
34
|
+
0x9c => 0x00de, # LATIN CAPITAL LETTER THORN
|
35
|
+
0x9d => 0x00b5, # MICRO SIGN
|
36
|
+
0x9e => 0x00d7, # MULTIPLICATION SIGN
|
37
|
+
0x9f => 0x00f7, # DIVISION SIGN
|
38
|
+
0xa0 => 0x00a9, # COPYRIGHT SIGN
|
39
|
+
0xa1 => 0x00a1, # INVERTED EXCLAMATION MARK
|
40
|
+
0xa2 => 0x00a2, # CENT SIGN
|
41
|
+
0xa3 => 0x00a3, # POUND SIGN
|
42
|
+
0xa4 => 0x2044, # FRACTION SLASH
|
43
|
+
0xa5 => 0x00a5, # YEN SIGN
|
44
|
+
0xa6 => 0x0192, # LATIN SMALL LETTER F WITH HOOK
|
45
|
+
0xa7 => 0x00a7, # SECTION SIGN
|
46
|
+
0xa8 => 0x00a4, # CURRENCY SIGN
|
47
|
+
0xa9 => 0x2019, # RIGHT SINGLE QUOTATION MARK
|
48
|
+
0xaa => 0x201c, # LEFT DOUBLE QUOTATION MARK
|
49
|
+
0xab => 0x00ab, # LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
|
50
|
+
0xac => 0x2039, # SINGLE LEFT-POINTING ANGLE QUOTATION MARK
|
51
|
+
0xad => 0x203a, # SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
|
52
|
+
0xae => 0xfb01, # LATIN SMALL LIGATURE FI
|
53
|
+
0xaf => 0xfb02, # LATIN SMALL LIGATURE FL
|
54
|
+
0xb0 => 0x00ae, # REGISTERED SIGN
|
55
|
+
0xb1 => 0x2013, # EN DASH
|
56
|
+
0xb2 => 0x2020, # DAGGER
|
57
|
+
0xb3 => 0x2021, # DOUBLE DAGGER
|
58
|
+
0xb4 => 0x00b7, # MIDDLE DOT
|
59
|
+
0xb5 => 0x00a6, # BROKEN BAR
|
60
|
+
0xb6 => 0x00b6, # PILCROW SIGN
|
61
|
+
0xb7 => 0x2022, # BULLET
|
62
|
+
0xb8 => 0x201a, # SINGLE LOW-9 QUOTATION MARK
|
63
|
+
0xb9 => 0x201e, # DOUBLE LOW-9 QUOTATION MARK
|
64
|
+
0xba => 0x201d, # RIGHT DOUBLE QUOTATION MARK
|
65
|
+
0xbb => 0x00bb, # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
|
66
|
+
0xbc => 0x2026, # HORIZONTAL ELLIPSIS
|
67
|
+
0xbd => 0x2030, # PER MILLE SIGN
|
68
|
+
0xbe => 0x00ac, # NOT SIGN
|
69
|
+
0xbf => 0x00bf, # INVERTED QUESTION MARK
|
70
|
+
0xc0 => 0x00b9, # SUPERSCRIPT ONE
|
71
|
+
0xc1 => 0x02cb, # MODIFIER LETTER GRAVE ACCENT
|
72
|
+
0xc2 => 0x00b4, # ACUTE ACCENT
|
73
|
+
0xc3 => 0x02c6, # MODIFIER LETTER CIRCUMFLEX ACCENT
|
74
|
+
0xc4 => 0x02dc, # SMALL TILDE
|
75
|
+
0xc5 => 0x00af, # MACRON
|
76
|
+
0xc6 => 0x02d8, # BREVE
|
77
|
+
0xc7 => 0x02d9, # DOT ABOVE
|
78
|
+
0xc8 => 0x00a8, # DIAERESIS
|
79
|
+
0xc9 => 0x00b2, # SUPERSCRIPT TWO
|
80
|
+
0xca => 0x02da, # RING ABOVE
|
81
|
+
0xcb => 0x00b8, # CEDILLA
|
82
|
+
0xcc => 0x00b3, # SUPERSCRIPT THREE
|
83
|
+
0xcd => 0x02dd, # DOUBLE ACUTE ACCENT
|
84
|
+
0xce => 0x02db, # OGONEK
|
85
|
+
0xcf => 0x02c7, # CARON
|
86
|
+
0xd0 => 0x2014, # EM DASH
|
87
|
+
0xd1 => 0x00b1, # PLUS-MINUS SIGN
|
88
|
+
0xd2 => 0x00bc, # VULGAR FRACTION ONE QUARTER
|
89
|
+
0xd3 => 0x00bd, # VULGAR FRACTION ONE HALF
|
90
|
+
0xd4 => 0x00be, # VULGAR FRACTION THREE QUARTERS
|
91
|
+
0xd5 => 0x00e0, # LATIN SMALL LETTER A WITH GRAVE
|
92
|
+
0xd6 => 0x00e1, # LATIN SMALL LETTER A WITH ACUTE
|
93
|
+
0xd7 => 0x00e2, # LATIN SMALL LETTER A WITH CIRCUMFLEX
|
94
|
+
0xd8 => 0x00e3, # LATIN SMALL LETTER A WITH TILDE
|
95
|
+
0xd9 => 0x00e4, # LATIN SMALL LETTER A WITH DIAERESIS
|
96
|
+
0xda => 0x00e5, # LATIN SMALL LETTER A WITH RING ABOVE
|
97
|
+
0xdb => 0x00e7, # LATIN SMALL LETTER C WITH CEDILLA
|
98
|
+
0xdc => 0x00e8, # LATIN SMALL LETTER E WITH GRAVE
|
99
|
+
0xdd => 0x00e9, # LATIN SMALL LETTER E WITH ACUTE
|
100
|
+
0xde => 0x00ea, # LATIN SMALL LETTER E WITH CIRCUMFLEX
|
101
|
+
0xdf => 0x00eb, # LATIN SMALL LETTER E WITH DIAERESIS
|
102
|
+
0xe0 => 0x00ec, # LATIN SMALL LETTER I WITH GRAVE
|
103
|
+
0xe1 => 0x00c6, # LATIN CAPITAL LETTER AE
|
104
|
+
0xe2 => 0x00ed, # LATIN SMALL LETTER I WITH ACUTE
|
105
|
+
0xe3 => 0x00aa, # FEMININE ORDINAL INDICATOR
|
106
|
+
0xe4 => 0x00ee, # LATIN SMALL LETTER I WITH CIRCUMFLEX
|
107
|
+
0xe5 => 0x00ef, # LATIN SMALL LETTER I WITH DIAERESIS
|
108
|
+
0xe6 => 0x00f0, # LATIN SMALL LETTER ETH
|
109
|
+
0xe7 => 0x00f1, # LATIN SMALL LETTER N WITH TILDE
|
110
|
+
0xe8 => 0x0141, # LATIN CAPITAL LETTER L WITH STROKE
|
111
|
+
0xe9 => 0x00d8, # LATIN CAPITAL LETTER O WITH STROKE
|
112
|
+
0xea => 0x0152, # LATIN CAPITAL LIGATURE OE
|
113
|
+
0xeb => 0x00ba, # MASCULINE ORDINAL INDICATOR
|
114
|
+
0xec => 0x00f2, # LATIN SMALL LETTER O WITH GRAVE
|
115
|
+
0xed => 0x00f3, # LATIN SMALL LETTER O WITH ACUTE
|
116
|
+
0xee => 0x00f4, # LATIN SMALL LETTER O WITH CIRCUMFLEX
|
117
|
+
0xef => 0x00f5, # LATIN SMALL LETTER O WITH TILDE
|
118
|
+
0xf0 => 0x00f6, # LATIN SMALL LETTER O WITH DIAERESIS
|
119
|
+
0xf1 => 0x00e6, # LATIN SMALL LETTER AE
|
120
|
+
0xf2 => 0x00f9, # LATIN SMALL LETTER U WITH GRAVE
|
121
|
+
0xf3 => 0x00fa, # LATIN SMALL LETTER U WITH ACUTE
|
122
|
+
0xf4 => 0x00fb, # LATIN SMALL LETTER U WITH CIRCUMFLEX
|
123
|
+
0xf5 => 0x0131, # LATIN SMALL LETTER DOTLESS I
|
124
|
+
0xf6 => 0x00fc, # LATIN SMALL LETTER U WITH DIAERESIS
|
125
|
+
0xf7 => 0x00fd, # LATIN SMALL LETTER Y WITH ACUTE
|
126
|
+
0xf8 => 0x0142, # LATIN SMALL LETTER L WITH STROKE
|
127
|
+
0xf9 => 0x00f8, # LATIN SMALL LETTER O WITH STROKE
|
128
|
+
0xfa => 0x0153, # LATIN SMALL LIGATURE OE
|
129
|
+
0xfb => 0x00df, # LATIN SMALL LETTER SHARP S
|
130
|
+
0xfc => 0x00fe, # LATIN SMALL LETTER THORN
|
131
|
+
0xfd => 0x00ff, # LATIN SMALL LETTER Y WITH DIAERESIS
|
132
|
+
0xfe => 0xfffd, # .notdef, REPLACEMENT CHARACTER
|
133
|
+
0xff => 0xfffd, # .notdef, REPLACEMENT CHARACTER
|
134
|
+
}.freeze
|
135
|
+
end
|
136
|
+
end
|