rotuka-rutils 0.2.4

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