gilenson 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/lib/gilenson.rb ADDED
@@ -0,0 +1,572 @@
1
+ class Gilenson
2
+ VERSION = '1.0.5'
3
+ attr_accessor :glyph
4
+ attr_accessor :settings
5
+
6
+ # Выбрасывается если форматтеру задается неизвестная настройка
7
+ class Gilenson::UnknownSetting < RuntimeError
8
+ end
9
+
10
+ SETTINGS = {
11
+ "inches" => true, # преобразовывать дюймы в знак дюйма;
12
+ "laquo" => true, # кавычки-ёлочки
13
+ "quotes" => true, # кавычки-английские лапки
14
+ "dash" => true, # короткое тире (150)
15
+ "emdash" => true, # длинное тире двумя минусами (151)
16
+ "initials" => true, # тонкие шпации в инициалах
17
+ "copypaste" => false, # замена непечатных и "специальных" юникодных символов на entities
18
+ "(c)" => true, # обрабатывать знак копирайта
19
+ "(r)" => true,
20
+ "(tm)" => true,
21
+ "(p)" => true,
22
+ "acronyms" => true, # Акронимы с пояснениями - ЖЗЛ(Жизнь Замечатльных Людей)
23
+ "+-" => true, # спецсимволы, какие - понятно
24
+ "degrees" => true, # знак градуса
25
+ "dashglue" => true, "wordglue" => true, # приклеивание предлогов и дефисов
26
+ "spacing" => true, # запятые и пробелы, перестановка
27
+ "phones" => true, # обработка телефонов
28
+ "html" => true, # разрешение использования тагов html
29
+ "de_nobr" => false, # при true все <nobr/> заменяются на <span class="nobr"/>
30
+ "raw_output" => false, # выводить UTF-8 вместо entities
31
+ "skip_attr" => false, # при true не отрабатывать типографику в атрибутах тегов
32
+ "skip_code" => true, # при true не отрабатывать типографику внутри <code/>, <tt/>, CDATA
33
+ "enforce_en_quotes" => false, # только латинские кавычки
34
+ "enforce_ru_quotes" => false, # только русские кавычки (enforce_en_quotes при этом игнорируется)
35
+ }.freeze
36
+
37
+ # Глифы, использующиеся в подстановках по-умолчанию
38
+ GLYPHS = {
39
+ :quot => "&#34;", # quotation mark
40
+ :amp => "&#38;", # ampersand
41
+ :apos => "&#39;", # apos
42
+ :gt => "&#62;", # greater-than sign
43
+ :lt => "&#60;", # less-than sign
44
+ :nbsp => "&#160;", # non-breaking space
45
+ :sect => "&#167;", # section sign
46
+ :copy => "&#169;", # copyright sign
47
+ :laquo => "&#171;", # left-pointing double angle quotation mark = left pointing guillemet
48
+ :reg => "&#174;", # registered sign = registered trade mark sign
49
+ :deg => "&#176;", # degree sign
50
+ :plusmn => "&#177;", # plus-minus sign = plus-or-minus sign
51
+ :para => "&#182;", # pilcrow sign = paragraph sign
52
+ :middot => "&#183;", # middle dot = Georgian comma = Greek middle dot
53
+ :raquo => "&#187;", # right-pointing double angle quotation mark = right pointing guillemet
54
+ :ndash => "&#8211;", # en dash
55
+ :mdash => "&#8212;", # em dash
56
+ :lsquo => "&#8216;", # left single quotation mark
57
+ :rsquo => "&#8217;", # right single quotation mark
58
+ :ldquo => "&#8220;", # left double quotation mark
59
+ :rdquo => "&#8221;", # right double quotation mark
60
+ :bdquo => "&#8222;", # double low-9 quotation mark
61
+ :bull => "&#8226;", # bullet = black small circle
62
+ :hellip => "&#8230;", # horizontal ellipsis = three dot leader
63
+ :numero => "&#8470;", # numero
64
+ :trade => "&#8482;", # trade mark sign
65
+ :minus => "&#8722;", # minus sign
66
+ :inch => "&#8243;", # inch/second sign (u0x2033) (не путать с кавычками!)
67
+ :thinsp => "&#8201;", # полукруглая шпация (тонкий пробел)
68
+ :nob_open => '<span class="nobr">', # открывающий блок без переноса слов
69
+ :nob_close => '</span>', # закрывающий блок без переноса слов
70
+ }.freeze
71
+
72
+ # Нормальные "типографские" символы в UTF-виде. Браузерами обрабатываются плохонько, поэтому
73
+ # лучше заменять их на entities.
74
+ VERBATIM_GLYPHS = {
75
+ ' ' => :nbsp,# alt+0160 (NBSP here)
76
+ '«' => :laquo,
77
+ '»' => :raquo,
78
+ '§' => :sect,
79
+ '©' => :copy,
80
+ '®' => :reg,
81
+ '°' => :deg,
82
+ '±' => :plusmn,
83
+ '¶' => :para,
84
+ '·' => :middot,
85
+ '–' => :ndash,
86
+ '—' => :mdash,
87
+ '‘' => :lsquo,
88
+ '’' => :rsquo,
89
+ '“' => :ldquo,
90
+ '”' => :rdquo,
91
+ '„' => :bdquo,
92
+ '•' => :bull,
93
+ '…' => :hellip,
94
+ '№' => :numero,
95
+ '™' => :trade,
96
+ '−' => :minus,
97
+ ' ' => :thinsp,
98
+ '″' => :inch,
99
+ }.freeze
100
+
101
+ # Метка на которую подменяются вынутые теги - Unicode Character 'OBJECT REPLACEMENT CHARACTER' (U+FFFC)
102
+ # http://unicode.org/reports/tr20/tr20-1.html
103
+ # Он официально применяется для обозначения вложенного обьекта
104
+ REPLACEMENT_MARKER = [0xEF, 0xBF, 0xBC].pack("U*").freeze
105
+
106
+ # Кто придумал &#147;? Не учите людей плохому...
107
+ # Привет А.Лебедеву http://www.artlebedev.ru/kovodstvo/62/
108
+ # Используем символы, потом берем по символам из glyphs форматтера.
109
+ # Молодец mash!
110
+ FORBIDDEN_NUMERIC_ENTITIES = {
111
+ '132' => :bdquo,
112
+ '133' => :hellip,
113
+ '146' => :apos,
114
+ '147' => :ldquo,
115
+ '148' => :rdquo,
116
+ '149' => :bull,
117
+ '150' => :ndash,
118
+ '151' => :mdash,
119
+ '153' => :trade,
120
+ }.freeze
121
+
122
+ # Все юникодные пробелы, шпации и отступы
123
+ UNICODE_WHITESPACE = [
124
+ (0x0009..0x000D).to_a, # White_Space # Cc [5] <control-0009>..<control-000D>
125
+ 0x0020, # White_Space # Zs SPACE
126
+ 0x0085, # White_Space # Cc <control-0085>
127
+ 0x00A0, # White_Space # Zs NO-BREAK SPACE
128
+ 0x1680, # White_Space # Zs OGHAM SPACE MARK
129
+ 0x180E, # White_Space # Zs MONGOLIAN VOWEL SEPARATOR
130
+ (0x2000..0x200A).to_a, # White_Space # Zs [11] EN QUAD..HAIR SPACE
131
+ 0x2028, # White_Space # Zl LINE SEPARATOR
132
+ 0x2029, # White_Space # Zp PARAGRAPH SEPARATOR
133
+ 0x202F, # White_Space # Zs NARROW NO-BREAK SPACE
134
+ 0x205F, # White_Space # Zs MEDIUM MATHEMATICAL SPACE
135
+ 0x3000, # White_Space # Zs IDEOGRAPHIC SPACE
136
+ ].flatten.pack("U*").freeze
137
+
138
+ PROTECTED_SETTINGS = [ :raw_output ] #:nodoc:
139
+
140
+ def initialize(*args)
141
+ @_text = args[0].is_a?(String) ? args[0] : ''
142
+ setup_default_settings!
143
+ accept_configuration_arguments!(args.last) if args.last.is_a?(Hash)
144
+ end
145
+
146
+ # Настраивает форматтер ассоциированным хешем
147
+ # formatter.configure!(:dash=>true, :wordglue=>false)
148
+ def configure!(*config)
149
+ accept_configuration_arguments!(config.last) if config.last.is_a?(Hash)
150
+ end
151
+
152
+ alias :configure :configure! #Дружественный API
153
+
154
+ # Неизвестные методы - настройки. С = - установка ключа, без - получение значения
155
+ def method_missing(meth, *args) #:nodoc:
156
+ setting = meth.to_s.gsub(/=$/, '')
157
+ super(meth, *args) unless @settings.has_key?(setting) #this will pop the exception if we have no such setting
158
+
159
+ return (@settings[setting] = args[0])
160
+ end
161
+
162
+ # Обрабатывает text_to_process с сохранением настроек, присвоенных обьекту-форматтеру
163
+ # Дополнительные аргументы передаются как параметры форматтера и не сохраняются после прогона.
164
+ def process(text_to_process, *args)
165
+ @_text = text_to_process
166
+
167
+ args.last.is_a?(Hash) ? with_configuration(args.last) { to_html } : to_html
168
+ end
169
+
170
+ # Обрабатывает текст, присвоенный форматтеру при создании и возвращает результат обработки
171
+ def to_html
172
+ return '' unless @_text
173
+
174
+ # NOTE: strip is Unicode-space aware on 1.9.1, so here we simulate that
175
+ text = @_text.gsub(/[#{UNICODE_WHITESPACE}]\z/, '').gsub(/\A[#{UNICODE_WHITESPACE}]/, '')
176
+
177
+ # -6. Подмухляем таблицу глифов, если нам ее передали
178
+ glyph_table = glyph.dup
179
+
180
+ if @settings["enforce_ru_quotes"]
181
+ glyph_table[:ldquo], glyph_table[:rdquo] = glyph_table[:laquo], glyph_table[:raquo]
182
+ elsif @settings["enforce_en_quotes"]
183
+ glyph_table[:laquo], glyph_table[:raquo] = glyph_table[:ldquo], glyph_table[:rdquo]
184
+ end
185
+
186
+ # -5. Копируем глифы в ивары, к ним доступ быстр и в коде они глаза тоже не мозолят
187
+ glyph_table.each_pair do | ki, wi |
188
+ instance_variable_set("@#{ki}", wi)
189
+ end
190
+
191
+ # -4. запрет тагов html
192
+ process_escape_html(text) unless @settings["html"]
193
+
194
+ # -3. Никогда (вы слышите?!) не пущать лабуду &#not_correct_number;
195
+ FORBIDDEN_NUMERIC_ENTITIES.dup.each_pair do | key, rep |
196
+ text.gsub!(/&##{key};/, self.glyph[rep])
197
+ end
198
+
199
+ # -2. Чистим copy&paste
200
+ process_copy_paste_clearing(text) if @settings['copypaste']
201
+
202
+ # -1. Замена &entity_name; на входе ('&nbsp;' => '&#160;' и т.д.)
203
+ process_html_entities(text)
204
+
205
+ # 0. Вырезаем таги
206
+ tags = lift_ignored_elements(text) if @skip_tags
207
+
208
+ # 1. Запятые и пробелы
209
+ process_spacing(text) if @settings["spacing"]
210
+
211
+ # 1. лапки
212
+ process_quotes(text) if @settings["quotes"]
213
+
214
+ # 2. ёлочки
215
+ process_laquo(text) if @settings["laquo"]
216
+
217
+ # 3. Инчи
218
+ process_inches(text) if @settings["inches"]
219
+
220
+ # 2b. одновременно ёлочки и лапки
221
+ process_compound_quotes(text) if (@settings["quotes"] && @settings["laquo"])
222
+
223
+ # 3. тире
224
+ process_dash(text) if @settings["dash"]
225
+
226
+ # 3a. тире длинное
227
+ process_emdash(text) if @settings["emdash"]
228
+
229
+ # 5. +/-
230
+ process_plusmin(text) if @settings["+-"]
231
+
232
+ # 5a. 12^C
233
+ process_degrees(text) if @settings["degrees"]
234
+
235
+ # 6. телефоны
236
+ process_phones(text) if @settings["phones"]
237
+
238
+ # 7. Короткие слова и &nbsp;
239
+ process_wordglue(text) if @settings["wordglue"]
240
+
241
+ # 8. Склейка ласт. Тьфу! дефисов.
242
+ process_dashglue(text) if @settings["dashglue"]
243
+
244
+ # 8a. Инициалы
245
+ process_initials(text) if @settings['initials']
246
+
247
+ # 8b. Троеточия
248
+ process_ellipsises(text) if @settings["wordglue"]
249
+
250
+ # 9. Акронимы от Текстиля
251
+ process_acronyms(text) if @settings["acronyms"]
252
+
253
+ # БЕСКОНЕЧНОСТЬ. Вставляем таги обратно.
254
+ reinsert_fragments(text, tags) if @skip_tags
255
+
256
+ # фуф, закончили.
257
+ process_span_instead_of_nobr(text) if @settings["de_nobr"]
258
+
259
+ # заменяем entities на истинные символы
260
+ process_raw_output(text) if @settings["raw_output"]
261
+
262
+ text.strip
263
+ end
264
+
265
+
266
+ # Применяет отдельный фильтр к text и возвращает результат. Например:
267
+ # formatter.apply(:wordglue, "Вот так") => "Вот&#160;так"
268
+ # Удобно применять когда вам нужно задействовать отдельный фильтр Гиленсона, но не нужна остальная механика
269
+ # Последний аргумент определяет, нужно ли при применении фильтра сохранить в неприкосновенности таги и другие
270
+ # игнорируемые фрагменты текста (по умолчанию они сохраняются).
271
+ def apply(filter, text, lift_ignored_elements = true)
272
+ copy = text.dup
273
+ unless lift_ignored_elements
274
+ self.send("process_#{filter}".to_sym, copy)
275
+ else
276
+ lifting_fragments(copy) { self.send("process_#{filter}".to_sym, copy) }
277
+ end
278
+ copy
279
+ end
280
+
281
+ private
282
+
283
+ def setup_default_settings!
284
+ @skip_tags = true;
285
+ @ignore = /notypo/ # regex, который игнорируется. Этим надо воспользоваться для обработки pre и code
286
+
287
+ @glueleft = ['рис.', 'табл.', 'см.', 'им.', 'ул.', 'пер.', 'кв.', 'офис', 'оф.', 'г.']
288
+ @glueright = ['руб.', 'коп.', 'у.е.', 'мин.']
289
+
290
+ # Установки можно менять в каждом экземпляре
291
+ @settings = SETTINGS.dup
292
+
293
+ @mark_tag = REPLACEMENT_MARKER
294
+ # Глифы можено подменять в экземпляре форматтера поэтому копируем их из константы
295
+ @glyph = GLYPHS.dup
296
+
297
+ @phonemasks = [[ /([0-9]{4})\-([0-9]{2})\-([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})/,
298
+ /([0-9]{4})\-([0-9]{2})\-([0-9]{2})/,
299
+ /(\([0-9\+\-]+\)) ?([0-9]{3})\-([0-9]{2})\-([0-9]{2})/,
300
+ /(\([0-9\+\-]+\)) ?([0-9]{2})\-([0-9]{2})\-([0-9]{2})/,
301
+ /(\([0-9\+\-]+\)) ?([0-9]{3})\-([0-9]{2})/,
302
+ /(\([0-9\+\-]+\)) ?([0-9]{2})\-([0-9]{3})/,
303
+ /([0-9]{3})\-([0-9]{2})\-([0-9]{2})/,
304
+ /([0-9]{2})\-([0-9]{2})\-([0-9]{2})/,
305
+ /([0-9]{1})\-([0-9]{2})\-([0-9]{2})/,
306
+ /([0-9]{2})\-([0-9]{3})/,
307
+ /([0-9]+)\-([0-9]+)/,
308
+ ],[
309
+ ':nob_open\1:ndash\2:ndash\3:nbsp\4:\5:\6:nob_close',
310
+ ':nob_open\1:ndash\2:ndash\3:nob_close',
311
+ ':nob_open\1:nbsp\2:ndash\3:ndash\4:nob_close',
312
+ ':nob_open\1:nbsp\2:ndash\3:ndash\4:nob_close',
313
+ ':nob_open\1:nbsp\2:ndash\3:nob_close',
314
+ ':nob_open\1:nbsp\2:ndash\3:nob_close',
315
+ ':nob_open\1:ndash\2:ndash\3:nob_close',
316
+ ':nob_open\1:ndash\2:ndash\3:nob_close',
317
+ ':nob_open\1:ndash\2:ndash\3:nob_close',
318
+ ':nob_open\1:ndash\2:nob_close',
319
+ ':nob_open\1:ndash\2:nob_close'
320
+ ]]
321
+ end
322
+
323
+ # Позволяет получить процедуру, при вызове возвращающую значение глифа
324
+ # def lookup(glyph_to_lookup)
325
+ # return Proc.new { g[glyph_to_lookup] }
326
+ # end
327
+
328
+ # Подставляет "символы" (двоеточие + имя глифа) на нужное значение глифа заданное в данном форматтере
329
+ def substitute_glyphs_in_string(str)
330
+ re = str.dup
331
+ @glyph.each_pair do | key, subst |
332
+ re.gsub!(":#{key.to_s}", subst)
333
+ end
334
+ re
335
+ end
336
+
337
+ # Выполняет блок, временно включая настройки переданные в +hash+
338
+ def with_configuration(hash, &block)
339
+ old_settings, old_glyphs = @settings.dup, @glyph.dup
340
+ accept_configuration_arguments!(hash)
341
+ txt = yield
342
+ @settings, @glyph = old_settings, old_glyphs
343
+
344
+ return txt
345
+ end
346
+
347
+ def accept_configuration_arguments!(args_hash)
348
+
349
+ # Специальный случай - :all=>true|false
350
+ if args_hash.has_key?(:all)
351
+ if args_hash[:all]
352
+ @settings.each_pair {|k, v| @settings[k] = true unless PROTECTED_SETTINGS.include?(k.to_sym)}
353
+ else
354
+ @settings.each_pair {|k, v| @settings[k] = false unless PROTECTED_SETTINGS.include?(k.to_sym)}
355
+ end
356
+ else
357
+
358
+ # Кинуть ошибку если настройка нам неизвестна
359
+ unknown_settings = args_hash.keys.collect{|k|k.to_s} - @settings.keys.collect { |k| k.to_s }
360
+ raise UnknownSetting, unknown_settings if unknown_settings.any?
361
+
362
+ args_hash.each_pair do | key, value |
363
+ @settings[key.to_s] = (value ? true : false)
364
+ end
365
+ end
366
+ end
367
+
368
+ # Вынимает игнорируемые фрагменты и заменяет их маркером, выполняет переданный блок и вставляет вынутое на место
369
+ def lifting_fragments(text, &block)
370
+ lifted = lift_ignored_elements(text)
371
+ yield
372
+ reinsert_fragments(text, lifted)
373
+ end
374
+
375
+ #Вынимает фрагменты из текста и возвращает массив с фрагментами
376
+ def lift_ignored_elements(text)
377
+ # re = /<\/?[a-z0-9]+("+ # имя тага
378
+ # "\s+("+ # повторяющая конструкция: хотя бы один разделитель и тельце
379
+ # "[a-z]+("+ # атрибут из букв, за которым может стоять знак равенства и потом
380
+ # "=((\'[^\']*\')|(\"[^\"]*\")|([0-9@\-_a-z:\/?&=\.]+))"+ #
381
+ # ")?"+
382
+ # ")?"+
383
+ # ")*\/?>|\xA2\xA2[^\n]*?==/i;
384
+
385
+ re_skipcode = '((<(code|tt)[ >](.*?)<\/(code|tt)>)|(<!\[CDATA\[(.*?)\]\]>))|' if @settings['skip_code']
386
+ re = /(#{re_skipcode}<\/?[a-z0-9]+(\s+([a-z]+(=((\'[^\']*\')|(\"[^\"]*\")|([0-9@\-_a-z:\/?&=\.]+)))?)?)*\/?>)/uim
387
+ tags = text.scan(re).map{ |tag| tag[0] } # первая группа!
388
+ text.gsub!(re, @mark_tag) #маркер тега, мы используем Invalid UTF-sequence для него
389
+ return tags
390
+ end
391
+
392
+ def reinsert_fragments(text, fragments)
393
+ fragments.each do |fragment|
394
+ fragment.gsub!(/ (href|src|data)=((?:(\')([^\']*)(\'))|(?:(\")([^\"]*)(\")))/uim) do
395
+ " #{$1}=" + $2.gsub(/&(?!(#0*38)|(amp);)/, @amp)
396
+ end # unless @settings['raw_output'] -- делать это надо всегда (mash)
397
+
398
+ unless @settings['skip_attr']
399
+ fragment.gsub!(/ (title|alt)=((?:(\')([^\']*)(\'))|(?:(\")([^\"]*)(\")))/uim) do
400
+ " #{$1}=#{$3}" + self.process($4.to_s) + "#{$5}#{$6}" + self.process($7.to_s) + "#{$8}"
401
+ end
402
+ end
403
+ text.sub!(@mark_tag, fragment)
404
+ end
405
+ end
406
+
407
+ ### Имплементации фильтров
408
+ def process_html_entities(text)
409
+ self.glyph.each { |key, value| text.gsub!(/&#{key};/, value)}
410
+ end
411
+
412
+ def process_initials(text)
413
+ initials = /([А-Я])[\.]{1,2}[\s]*?([А-Я])[\.]*[\s]*?([А-Я])([а-я])/u
414
+ replacement = substitute_glyphs_in_string('\1.\2.:thinsp\3\4')
415
+ text.gsub!(initials, replacement)
416
+ end
417
+
418
+ def process_copy_paste_clearing(text)
419
+ VERBATIM_GLYPHS.each {|key,value| text.gsub!(/#{Regexp.escape(key)}/, glyph[value]) }
420
+ end
421
+
422
+ def process_spacing(text)
423
+ text.gsub!( /(\s*)([,]*)/sui, '\2\1');
424
+ text.gsub!( /(\s*)([\.?!]*)(\s*[ЁА-ЯA-Z])/su, '\2\1\3');
425
+ end
426
+
427
+ def process_dashglue(text)
428
+ text.gsub!( /([a-zа-яА-Я0-9]+(\-[a-zа-яА-Я0-9]+)+)/ui, @nob_open+'\1'+ @nob_close)
429
+ end
430
+
431
+ def process_escape_html(text)
432
+ text.gsub!(/&/, @amp)
433
+ text.gsub!(/</, @lt)
434
+ text.gsub!(/>/, @gt)
435
+ end
436
+
437
+ def process_span_instead_of_nobr(text)
438
+ text.gsub!(/<nobr>/, '<span class="nobr">')
439
+ text.gsub!(/<\/nobr>/, '</span>')
440
+ end
441
+
442
+ def process_dash(text)
443
+ text.gsub!( /(\s|;)\-(\s)/ui, '\1'+@ndash+'\2')
444
+ end
445
+
446
+ def process_emdash(text)
447
+ text.gsub!( /(\s|;)\-\-(\s)/ui, '\1'+@mdash+'\2')
448
+ # 4. (с)
449
+ text.gsub!(/\([сСcC]\)((?=\w)|(?=\s[0-9]+))/u, @copy) if @settings["(c)"]
450
+ # 4a. (r)
451
+ text.gsub!( /\(r\)/ui, '<sup>'+@reg+'</sup>') if @settings["(r)"]
452
+
453
+ # 4b. (tm)
454
+ text.gsub!( /\(tm\)|\(тм\)/ui, @trade) if @settings["(tm)"]
455
+ # 4c. (p)
456
+ text.gsub!( /\(p\)/ui, @sect) if @settings["(p)"]
457
+ end
458
+
459
+ def process_ellipsises(text)
460
+ text.gsub!( '...', @hellip)
461
+ end
462
+
463
+ def process_laquo(text)
464
+ text.gsub!( /\"\"/ui, @quot * 2);
465
+ text.gsub!( /(^|\s|#{@mark_tag}|>|\()\"((#{@mark_tag})*[~0-9ёЁA-Za-zА-Яа-я\-:\/\.])/ui, '\1' + @laquo + '\2');
466
+ _text = '""';
467
+ until _text == text do
468
+ _text = text;
469
+ text.gsub!( /(#{@laquo}([^\"]*)[ёЁA-Za-zА-Яа-я0-9\.\-:\/\?\!](#{@mark_tag})*)\"/sui, '\1' + @raquo)
470
+ end
471
+ end
472
+
473
+ def process_quotes(text)
474
+ text.gsub!( /\"\"/ui, @quot*2)
475
+ text.gsub!( /\"\.\"/ui, @quot+"."+@quot)
476
+ _text = '""';
477
+ lat_c = '0-9A-Za-z'
478
+ punct = /\'\!\s\.\?\,\-\&\;\:\\/
479
+
480
+ until _text == text do
481
+ _text = text.dup
482
+ text.gsub!( /(^|\s|#{@mark_tag}|>)\"([#{lat_c}#{punct}\_\#{@mark_tag}]+(\"|#{@rdquo}))/ui, '\1'+ @ldquo +'\2')
483
+ text.gsub!( /(#{@ldquo}([#{lat_c}#{punct}#{@mark_tag}\_]*).*[#{lat_c}][\#{@mark_tag}\?\.\!\,\\]*)\"/ui, '\1'+ @rdquo)
484
+ end
485
+ end
486
+
487
+ def process_compound_quotes(text)
488
+ text.gsub!(/(#{@ldquo}(([A-Za-z0-9'!\.?,\-&;:]|\s|#{@mark_tag})*)#{@laquo}(.*)#{@raquo})#{@raquo}/ui, '\1' + @rdquo);
489
+ end
490
+
491
+ def process_degrees(text)
492
+ text.gsub!( /-([0-9])+\^([FCС])/, @ndash+'\1'+ @deg +'\2') #deg
493
+ text.gsub!( /\+([0-9])+\^([FCС])/, '+\1'+ @deg +'\2')
494
+ text.gsub!( /\^([FCС])/, @deg+'\1')
495
+ end
496
+
497
+ def process_wordglue(text)
498
+ text.replace(" " + text + " ")
499
+ _text = " " + text + " "
500
+
501
+ until _text == text
502
+ _text = text
503
+ text.gsub!( /(\s+)([a-zа-яА-Я0-9]{1,2})(\s+)([^\\s$])/ui, '\1\2' + @nbsp +'\4')
504
+ text.gsub!( /(\s+)([a-zа-яА-Я0-9]{3})(\s+)([^\\s$])/ui, '\1\2' + @nbsp+'\4')
505
+ end
506
+
507
+ # Пунктуация это либо один из наших глифов, либо мемберы класса. В данном случае
508
+ # мы цепляемся за кончик строки поэтому можум прихватить и глиф тоже
509
+ # Пунктуация включает наши собственные глифы!
510
+ punct = glyph.values.map{|v| Regexp.escape(v)}.join('|')
511
+ vpunct = /(#{punct}|[\)\]\!\?,\.;])/
512
+
513
+ text.gsub!(/(\s+)([a-zа-яА-Я0-9]{1,2}#{vpunct}{0,3}\s$)/ui, @nbsp+'\2')
514
+
515
+ @glueleft.each { | i | text.gsub!( /(\s)(#{i})(\s+)/sui, '\1\2' + @nbsp) }
516
+
517
+ @glueright.each { | i | text.gsub!( /(\s)(#{i})(\s+)/sui, @nbsp+'\2\3') }
518
+
519
+ end
520
+
521
+ def process_phones(text)
522
+ @phonemasks[0].each_with_index do |pattern, i|
523
+ replacement = substitute_glyphs_in_string(@phonemasks[1][i])
524
+ text.gsub!(pattern, replacement)
525
+ end
526
+ end
527
+
528
+ def process_acronyms(text)
529
+ acronym = /\b([A-ZА-Я][A-ZА-Я0-9]{2,})\b(?:[(]([^)]*)[)])/u
530
+ if @settings["raw_output"]
531
+ text.gsub!(acronym, '\1%s(\2)' % @thinsp)
532
+ else
533
+ text.gsub!(acronym) do
534
+ expl = $2.to_s; process_escape_html(expl)
535
+ "<acronym title=\"#{expl}\">#{$1}</acronym>"
536
+ end
537
+ end
538
+ end
539
+
540
+ # Обработка знака дюйма, кроме случаев когда он внутри кавычек
541
+ def process_inches(text)
542
+ text.gsub!(/\s([0-9]{1,2}([\.,][0-9]{1,2})?)(\"){1,1}/ui, ' \1' + @inch)
543
+ end
544
+
545
+ def process_plusmin(text)
546
+ text.gsub!(/[^+]\+\-/ui, @plusmn)
547
+ end
548
+
549
+ # Подменяет все юникодные entities в тексте на истинные UTF-8-символы
550
+ def process_raw_output(text)
551
+ # Все глифы
552
+ @glyph.values.each do | entity |
553
+ next unless entity =~ /^&#(\d+);/
554
+ text.gsub!(/#{entity}/, entity_to_raw_utf8(entity))
555
+ end
556
+ end
557
+
558
+ # Конвертирует юникодные entities в UTF-8-codepoints
559
+ def entity_to_raw_utf8(entity)
560
+ entity =~ /^&#(\d+);/
561
+ $1 ? [$1.to_i].pack("U") : entity
562
+ end
563
+
564
+ module StringFormatting
565
+ # Форматирует строку с помощью Gilenson::Formatter. Все дополнительные опции передаются форматтеру.
566
+ def gilensize(*args)
567
+ ::Gilenson.new(self, args.shift || {}).to_html
568
+ end
569
+ end
570
+ end
571
+
572
+ Object::String.send(:include, Gilenson::StringFormatting)