gilenson 1.0.5

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.
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)