rotuka-rutils 0.2.4

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