julik-rutils 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/History.txt +162 -0
  2. data/Manifest.txt +42 -0
  3. data/README.txt +234 -0
  4. data/Rakefile.rb +41 -0
  5. data/TODO.txt +6 -0
  6. data/WHAT_HAS_CHANGED.txt +44 -0
  7. data/bin/gilensize +22 -0
  8. data/bin/rutilize +38 -0
  9. data/init.rb +27 -0
  10. data/lib/countries/countries.rb +1773 -0
  11. data/lib/datetime/datetime.rb +83 -0
  12. data/lib/gilenson/bluecloth_extra.rb +7 -0
  13. data/lib/gilenson/gilenson.rb +665 -0
  14. data/lib/gilenson/helper.rb +34 -0
  15. data/lib/gilenson/maruku_extra.rb +19 -0
  16. data/lib/gilenson/rdiscount_extra.rb +7 -0
  17. data/lib/gilenson/redcloth_extra.rb +42 -0
  18. data/lib/integration/integration.rb +1 -0
  19. data/lib/integration/rails_date_helper_override.rb +127 -0
  20. data/lib/integration/rails_pre_filter.rb +4 -0
  21. data/lib/pluralizer/pluralizer.rb +261 -0
  22. data/lib/rutils.rb +54 -0
  23. data/lib/transliteration/bidi.rb +21 -0
  24. data/lib/transliteration/simple.rb +75 -0
  25. data/lib/transliteration/transliteration.rb +53 -0
  26. data/lib/version.rb +5 -0
  27. data/test/extras/integration_bluecloth.rb +13 -0
  28. data/test/extras/integration_maruku.rb +15 -0
  29. data/test/extras/integration_rails_filter.rb +29 -0
  30. data/test/extras/integration_rails_gilenson_helpers.rb +80 -0
  31. data/test/extras/integration_rails_helpers.rb +85 -0
  32. data/test/extras/integration_rdiscount.rb +15 -0
  33. data/test/extras/integration_redcloth3.rb +18 -0
  34. data/test/extras/integration_redcloth4.rb +19 -0
  35. data/test/run_tests.rb +4 -0
  36. data/test/test_datetime.rb +145 -0
  37. data/test/test_gilenson.rb +396 -0
  38. data/test/test_integration.rb +22 -0
  39. data/test/test_integration_flag.rb +18 -0
  40. data/test/test_pluralize.rb +84 -0
  41. data/test/test_rutils_base.rb +11 -0
  42. data/test/test_transliteration.rb +53 -0
  43. metadata +121 -0
@@ -0,0 +1,83 @@
1
+ # -*- encoding: utf-8 -*-
2
+ module RuTils
3
+ module DateTime
4
+
5
+ def self.distance_of_time_in_words(from_time, to_time = 0, include_seconds = false, absolute = false) #nodoc
6
+ from_time = from_time.to_time if from_time.respond_to?(:to_time)
7
+ to_time = to_time.to_time if to_time.respond_to?(:to_time)
8
+ distance_in_minutes = (((to_time - from_time).abs)/60).round
9
+ distance_in_seconds = ((to_time - from_time).abs).round
10
+
11
+ case distance_in_minutes
12
+ when 0..1
13
+ return (distance_in_minutes==0) ? 'меньше минуты' : '1 минуту' unless include_seconds
14
+
15
+ case distance_in_seconds
16
+ when 0..5 then 'менее 5 секунд'
17
+ when 6..10 then 'менее 10 секунд'
18
+ when 11..20 then 'менее 20 секунд'
19
+ when 21..40 then 'пол-минуты'
20
+ when 41..59 then 'меньше минуты'
21
+ else '1 минуту'
22
+ end
23
+
24
+ when 2..45 then distance_in_minutes.to_s +
25
+ " " + distance_in_minutes.items("минута", "минуты", "минут")
26
+ when 46..90 then 'около часа'
27
+ # исключение, сдвигаем на один влево чтобы соответствовать падежу
28
+ when 90..1440 then "около " + (distance_in_minutes.to_f / 60.0).round.to_s +
29
+ " " + (distance_in_minutes.to_f / 60.0).round.items("часа", 'часов', 'часов')
30
+ when 1441..2880 then '1 день'
31
+ else (distance_in_minutes / 1440).round.to_s +
32
+ " " + (distance_in_minutes / 1440).round.items("день", "дня", "дней")
33
+ end
34
+ end
35
+
36
+ def self.ru_strftime(time, format_str='%d.%m.%Y')
37
+ clean_fmt = format_str.to_s.gsub(/%{2}/, RuTils::SUBSTITUTION_MARKER).
38
+ gsub(/%a/, Date::RU_ABBR_DAYNAMES[time.wday]).
39
+ gsub(/%A/, Date::RU_DAYNAMES[time.wday]).
40
+ gsub(/%b/, Date::RU_ABBR_MONTHNAMES[time.mon]).
41
+ gsub(/%d(\s)*%B/, '%02d' % time.day + '\1' + Date::RU_INFLECTED_MONTHNAMES[time.mon]).
42
+ gsub(/%e(\s)*%B/, '%d' % time.day + '\1' + Date::RU_INFLECTED_MONTHNAMES[time.mon]).
43
+ gsub(/%B/, Date::RU_MONTHNAMES[time.mon]).
44
+ gsub(/#{RuTils::SUBSTITUTION_MARKER}/, '%%')
45
+
46
+ # Теперь когда все нужные нам маркеры заменены можно отдать это стандартному strftime
47
+ time.respond_to?(:strftime_norutils) ? time.strftime_norutils(clean_fmt) : time.to_time.strftime_norutils(clean_fmt)
48
+ end
49
+ end
50
+ end
51
+
52
+ class Date
53
+ RU_MONTHNAMES = [nil] + %w{ январь февраль март апрель май июнь июль август сентябрь октябрь ноябрь декабрь }
54
+ RU_DAYNAMES = %w(воскресенье понедельник вторник среда четверг пятница суббота)
55
+ RU_ABBR_MONTHNAMES = [nil] + %w{ янв фев мар апр май июн июл авг сен окт ноя дек }
56
+ RU_ABBR_DAYNAMES = %w(вск пн вт ср чт пт сб)
57
+ RU_INFLECTED_MONTHNAMES = [nil] + %w{ января февраля марта апреля мая июня июля августа сентября октября ноября декабря }
58
+ RU_DAYNAMES_E = [nil] + %w{первое второе третье четвёртое пятое шестое седьмое восьмое девятое десятое одиннадцатое двенадцатое тринадцатое четырнадцатое пятнадцатое шестнадцатое семнадцатое восемнадцатое девятнадцатое двадцатое двадцать тридцатое тридцатьпервое}
59
+ end
60
+
61
+ class Time
62
+ alias_method :strftime_norutils, :strftime
63
+
64
+ def strftime(fmt)
65
+ RuTils::overrides_enabled? ? RuTils::DateTime::ru_strftime(self, fmt) : strftime_norutils(fmt)
66
+ end
67
+ end
68
+
69
+ class Date
70
+ # Inside rails we have date formatting
71
+ if self.instance_methods.include?('strftime')
72
+ alias_method :strftime_norutils, :strftime
73
+ def strftime(fmt='%F')
74
+ RuTils::overrides_enabled? ? RuTils::DateTime::ru_strftime(self, fmt) : strftime_norutils(fmt)
75
+ end
76
+
77
+ else
78
+ def strftime(fmt='%F')
79
+ RuTils::overrides_enabled? ? RuTils::DateTime::ru_strftime(self, fmt) : to_time.strftime(fmt)
80
+ end
81
+ end
82
+
83
+ end
@@ -0,0 +1,7 @@
1
+ # -*- encoding: utf-8 -*-
2
+ # BlueCloth с поддержкой Gilenson
3
+ class RuTils::Gilenson::BlueClothExtra < BlueCloth
4
+ def to_html(*opts)
5
+ RuTils::Gilenson::Formatter.new(super(*opts)).to_html
6
+ end
7
+ end
@@ -0,0 +1,665 @@
1
+ # -*- encoding: utf-8 -*-
2
+ module RuTils
3
+ module Gilenson
4
+ # Позволяет возвращать класс форматтера при вызове
5
+ # RuTils::Gilenson.new
6
+ def self.new(*args) #:nodoc:
7
+ RuTils::Gilenson::Formatter.new(*args)
8
+ end
9
+
10
+ autoload :BlueClothExtra, File.dirname(__FILE__) + '/bluecloth_extra'
11
+ autoload :RedClothExtra, File.dirname(__FILE__) + '/redcloth_extra'
12
+ autoload :RDiscountExtra, File.dirname(__FILE__) + '/rdiscount_extra'
13
+ autoload :MarukuExtra, File.dirname(__FILE__) + '/maruku_extra'
14
+ autoload :Helper, File.dirname(__FILE__) + '/helper'
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
+ #
32
+ # ==Использование
33
+ # Быстрее всего - через метод ++gilensize++ для любой строковой переменной
34
+ # %{ И вот они таки "приехали"}.gilensize => 'И&#160;вот они&#160;таки &#171;приехали&#187;'
35
+ # Все дополнительные настройки в таком случае передаются форматтеру
36
+ # %{ И вот они таки "приехали"}.gilensize(:laquo=>false) => 'И&#160;вот они&#160;таки "приехали"'
37
+ #
38
+ # Если форматтер надо настроить более тонко, можно использовать его и так:
39
+ # typ = RuTils::Gilenson.new('Эти "так называемые" великие деятели')
40
+ # typ.to_html => 'Эти &#171;так называемые&#187; великие деятели'
41
+ #
42
+ # или как фильтр
43
+ # formatter = RuTils::Gilenson.new
44
+ # formatter.configure(:dash=>true)
45
+ # for string in strings
46
+ # puts formatter.process(string)
47
+ # end
48
+ #
49
+ # ==Настройки
50
+ # Настройки регулируются через методы
51
+ # formatter.dashglue = true
52
+ # или ассоциированным хешем
53
+ # formatter.configure!(:dash=>true, :quotes=>false)
54
+ #
55
+ # Хеш также можно передавать как последний аргумент методам process и to_html,
56
+ # в таком случае настройки будут применены только при этом вызове
57
+ #
58
+ # beautified = formatter.process(my_text, :dash=>true)
59
+ #
60
+ # В параметры можно подставить также ключ :all чтобы временно включить или выключить все фильтры
61
+ #
62
+ # beautified = formatter.process(my_text, :all=>true)
63
+ #
64
+ # Помимо этого можно пользоваться каждым фильтром по отдельности используя метод +apply+
65
+ #
66
+ # Можно менять глифы, которые форматтер использует для подстановок. К примеру,
67
+ # formatter.glyph[:nbsp] = '&nbsp;'
68
+ # заставит форматтер расставлять "традиционные" неразрывные пробелы. Именно это - большая глупость,
69
+ # но другие глифы заменить может быть нужно.
70
+ #
71
+ # ==Настройки форматтера
72
+ # "inches" - преобразовывать дюймы в знак дюйма;
73
+ # "laquo" - кавычки-ёлочки
74
+ # "quotes" - кавычки-английские лапки
75
+ # "dash" - проставлять короткое тире (150)
76
+ # "emdash" - длинное тире двумя минусами (151)
77
+ # "initials" - проставлять тонкие шпации в инициалах
78
+ # "copypaste" - замена непечатных и "специальных" юникодных символов на entities
79
+ # "(c)" - обрабатывать знак копирайта
80
+ # "(r)", "(tm)", "(p)", "+-" - спецсимволы, какие - понятно
81
+ # "acronyms" - сворачивание пояснений к аббревиатурам (пояснение - в скобках после аббревиатуры
82
+ # без пробела). В текстовой версии пояснение будет "приклеено" к аббревиатуре
83
+ # полукруглой шпацией
84
+ # "degrees" - знак градуса
85
+ # "dashglue", "wordglue" - приклеивание предлогов и дефисов
86
+ # "spacing" - запятые и пробелы, перестановка
87
+ # "phones" - обработка телефонов
88
+ # "html" - при false - запрет использования тагов html
89
+ # "de_nobr" - при true все <nobr/> заменяются на <span class="nobr"/>
90
+ # "raw_output" - (по умолчанию false) - при true вместо entities выводятся UTF-символы
91
+ # "skip_attr" - (по умолчанию false) - при true не отрабатывать типографику в атрибутах тегов (title, alt)
92
+ # "skip_code" - (по умолчанию true) - при true не отрабатывать типографику внутри <code/>, <tt/>, CDATA
93
+ class RuTils::Gilenson::Formatter
94
+ attr_accessor :glyph
95
+ attr_accessor :settings
96
+
97
+ SETTINGS = {
98
+ "inches" => true, # преобразовывать дюймы в знак дюйма;
99
+ "laquo" => true, # кавычки-ёлочки
100
+ "quotes" => true, # кавычки-английские лапки
101
+ "dash" => true, # короткое тире (150)
102
+ "emdash" => true, # длинное тире двумя минусами (151)
103
+ "initials" => true, # тонкие шпации в инициалах
104
+ "copypaste" => false, # замена непечатных и "специальных" юникодных символов на entities
105
+ "(c)" => true, # обрабатывать знак копирайта
106
+ "(r)" => true,
107
+ "(tm)" => true,
108
+ "(p)" => true,
109
+ "acronyms" => true, # Акронимы с пояснениями - ЖЗЛ(Жизнь Замечатльных Людей)
110
+ "+-" => true, # спецсимволы, какие - понятно
111
+ "degrees" => true, # знак градуса
112
+ "dashglue" => true, "wordglue" => true, # приклеивание предлогов и дефисов
113
+ "spacing" => true, # запятые и пробелы, перестановка
114
+ "phones" => true, # обработка телефонов
115
+ "html" => true, # разрешение использования тагов html
116
+ "de_nobr" => false, # при true все <nobr/> заменяются на <span class="nobr"/>
117
+ "raw_output" => false, # выводить UTF-8 вместо entities
118
+ "skip_attr" => false, # при true не отрабатывать типографику в атрибутах тегов
119
+ "skip_code" => true, # при true не отрабатывать типографику внутри <code/>, <tt/>, CDATA
120
+ "enforce_en_quotes" => false, # только латинские кавычки
121
+ "enforce_ru_quotes" => false, # только русские кавычки (enforce_en_quotes при этом игнорируется)
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
+ }.freeze
160
+
161
+ # Нормальные "типографские" символы в UTF-виде. Браузерами обрабатываются плохонько, поэтому
162
+ # лучше заменять их на entities.
163
+ VERBATIM_GLYPHS = {
164
+ ' ' => :nbsp,# alt+0160 (NBSP here)
165
+ '«' => :laquo,
166
+ '»' => :raquo,
167
+ '§' => :sect,
168
+ '©' => :copy,
169
+ '®' => :reg,
170
+ '°' => :deg,
171
+ '±' => :plusmn,
172
+ '¶' => :para,
173
+ '·' => :middot,
174
+ '–' => :ndash,
175
+ '—' => :mdash,
176
+ '‘' => :lsquo,
177
+ '’' => :rsquo,
178
+ '“' => :ldquo,
179
+ '”' => :rdquo,
180
+ '„' => :bdquo,
181
+ '•' => :bull,
182
+ '…' => :hellip,
183
+ '№' => :numero,
184
+ '™' => :trade,
185
+ '−' => :minus,
186
+ ' ' => :thinsp,
187
+ '″' => :inch,
188
+ }.freeze
189
+
190
+ # Метка на которую подменяются вынутые теги
191
+ REPLACEMENT_MARKER = RuTils::SUBSTITUTION_MARKER #:nodoc:
192
+
193
+ # Кто придумал &#147;? Не учите людей плохому...
194
+ # Привет А.Лебедеву http://www.artlebedev.ru/kovodstvo/62/
195
+ # Используем символы, потом берем по символам из glyphs форматтера.
196
+ # Молодец mash!
197
+ FORBIDDEN_NUMERIC_ENTITIES = {
198
+ '132' => :bdquo,
199
+ '133' => :hellip,
200
+ '146' => :apos,
201
+ '147' => :ldquo,
202
+ '148' => :rdquo,
203
+ '149' => :bull,
204
+ '150' => :ndash,
205
+ '151' => :mdash,
206
+ '153' => :trade,
207
+ }.freeze
208
+
209
+ # All the unicode whitespace
210
+ UNICODE_WHITESPACE = [
211
+ (0x0009..0x000D).to_a, # White_Space # Cc [5] <control-0009>..<control-000D>
212
+ 0x0020, # White_Space # Zs SPACE
213
+ 0x0085, # White_Space # Cc <control-0085>
214
+ 0x00A0, # White_Space # Zs NO-BREAK SPACE
215
+ 0x1680, # White_Space # Zs OGHAM SPACE MARK
216
+ 0x180E, # White_Space # Zs MONGOLIAN VOWEL SEPARATOR
217
+ (0x2000..0x200A).to_a, # White_Space # Zs [11] EN QUAD..HAIR SPACE
218
+ 0x2028, # White_Space # Zl LINE SEPARATOR
219
+ 0x2029, # White_Space # Zp PARAGRAPH SEPARATOR
220
+ 0x202F, # White_Space # Zs NARROW NO-BREAK SPACE
221
+ 0x205F, # White_Space # Zs MEDIUM MATHEMATICAL SPACE
222
+ 0x3000, # White_Space # Zs IDEOGRAPHIC SPACE
223
+ ].flatten.pack("U*").freeze
224
+
225
+ PROTECTED_SETTINGS = [ :raw_output ] #:nodoc:
226
+
227
+ def initialize(*args)
228
+ @_text = args[0].is_a?(String) ? args[0] : ''
229
+ setup_default_settings!
230
+ accept_configuration_arguments!(args.last) if args.last.is_a?(Hash)
231
+ end
232
+
233
+ # Настраивает форматтер ассоциированным хешем
234
+ # formatter.configure!(:dash=>true, :wordglue=>false)
235
+ def configure!(*config)
236
+ accept_configuration_arguments!(config.last) if config.last.is_a?(Hash)
237
+ end
238
+
239
+ alias :configure :configure! #Дружественный API
240
+
241
+ # Неизвестные методы - настройки. С = - установка ключа, без - получение значения
242
+ def method_missing(meth, *args) #:nodoc:
243
+ setting = meth.to_s.gsub(/=$/, '')
244
+ super(meth, *args) unless @settings.has_key?(setting) #this will pop the exception if we have no such setting
245
+
246
+ return (@settings[setting] = args[0])
247
+ end
248
+
249
+ # Обрабатывает text_to_process с сохранением настроек, присвоенных обьекту-форматтеру
250
+ # Дополнительные аргументы передаются как параметры форматтера и не сохраняются после прогона.
251
+ def process(text_to_process, *args)
252
+ @_text = text_to_process
253
+
254
+ args.last.is_a?(Hash) ? with_configuration(args.last) { to_html } : to_html
255
+ end
256
+
257
+ # Обрабатывает текст, присвоенный форматтеру при создании и возвращает результат обработки
258
+ def to_html
259
+ return '' unless @_text
260
+
261
+ # NOTE: strip is Unicode-space aware on 1.9.1, so here we simulate that
262
+ text = @_text.gsub(/[#{UNICODE_WHITESPACE}]\z/, '').gsub(/\A[#{UNICODE_WHITESPACE}]/, '')
263
+
264
+ # -6. Подмухляем таблицу глифов, если нам ее передали
265
+ glyph_table = glyph.dup
266
+
267
+ if @settings["enforce_ru_quotes"]
268
+ glyph_table[:ldquo], glyph_table[:rdquo] = glyph_table[:laquo], glyph_table[:raquo]
269
+ elsif @settings["enforce_en_quotes"]
270
+ glyph_table[:laquo], glyph_table[:raquo] = glyph_table[:ldquo], glyph_table[:rdquo]
271
+ end
272
+
273
+ # -5. Копируем глифы в ивары, к ним доступ быстр и в коде они глаза тоже не мозолят
274
+ glyph_table.each_pair do | ki, wi |
275
+ instance_variable_set("@#{ki}", wi)
276
+ end
277
+
278
+ # -4. запрет тагов html
279
+ process_escape_html(text) unless @settings["html"]
280
+
281
+ # -3. Никогда (вы слышите?!) не пущать лабуду &#not_correct_number;
282
+ FORBIDDEN_NUMERIC_ENTITIES.dup.each_pair do | key, rep |
283
+ text.gsub!(/&##{key};/, self.glyph[rep])
284
+ end
285
+
286
+ # -2. Чистим copy&paste
287
+ process_copy_paste_clearing(text) if @settings['copypaste']
288
+
289
+ # -1. Замена &entity_name; на входе ('&nbsp;' => '&#160;' и т.д.)
290
+ process_html_entities(text)
291
+
292
+ # 0. Вырезаем таги
293
+ tags = lift_ignored_elements(text) if @skip_tags
294
+
295
+ # 1. Запятые и пробелы
296
+ process_spacing(text) if @settings["spacing"]
297
+
298
+ # 1. лапки
299
+ process_quotes(text) if @settings["quotes"]
300
+
301
+ # 2. ёлочки
302
+ process_laquo(text) if @settings["laquo"]
303
+
304
+ # 3. Инчи
305
+ process_inches(text) if @settings["inches"]
306
+
307
+ # 2b. одновременно ёлочки и лапки
308
+ process_compound_quotes(text) if (@settings["quotes"] && @settings["laquo"])
309
+
310
+ # 3. тире
311
+ process_dash(text) if @settings["dash"]
312
+
313
+ # 3a. тире длинное
314
+ process_emdash(text) if @settings["emdash"]
315
+
316
+ # 5. +/-
317
+ process_plusmin(text) if @settings["+-"]
318
+
319
+ # 5a. 12^C
320
+ process_degrees(text) if @settings["degrees"]
321
+
322
+ # 6. телефоны
323
+ process_phones(text) if @settings["phones"]
324
+
325
+ # 7. Короткие слова и &nbsp;
326
+ process_wordglue(text) if @settings["wordglue"]
327
+
328
+ # 8. Склейка ласт. Тьфу! дефисов.
329
+ process_dashglue(text) if @settings["dashglue"]
330
+
331
+ # 8a. Инициалы
332
+ process_initials(text) if @settings['initials']
333
+
334
+ # 8b. Троеточия
335
+ process_ellipsises(text) if @settings["wordglue"]
336
+
337
+ # 9. Акронимы от Текстиля
338
+ process_acronyms(text) if @settings["acronyms"]
339
+
340
+ # БЕСКОНЕЧНОСТЬ. Вставляем таги обратно.
341
+ reinsert_fragments(text, tags) if @skip_tags
342
+
343
+ # фуф, закончили.
344
+ process_span_instead_of_nobr(text) if @settings["de_nobr"]
345
+
346
+ # заменяем entities на истинные символы
347
+ process_raw_output(text) if @settings["raw_output"]
348
+
349
+ text.strip
350
+ end
351
+
352
+
353
+ # Применяет отдельный фильтр к text и возвращает результат. Например:
354
+ # formatter.apply(:wordglue, "Вот так") => "Вот&#160;так"
355
+ # Удобно применять когда вам нужно задействовать отдельный фильтр Гиленсона, но не нужна остальная механика
356
+ # Последний аргумент определяет, нужно ли при применении фильтра сохранить в неприкосновенности таги и другие
357
+ # игнорируемые фрагменты текста (по умолчанию они сохраняются).
358
+ def apply(filter, text, lift_ignored_elements = true)
359
+ copy = text.dup
360
+ unless lift_ignored_elements
361
+ self.send("process_#{filter}".to_sym, copy)
362
+ else
363
+ lifting_fragments(copy) { self.send("process_#{filter}".to_sym, copy) }
364
+ end
365
+ copy
366
+ end
367
+
368
+ private
369
+
370
+ def setup_default_settings!
371
+ @skip_tags = true;
372
+ @ignore = /notypo/ # regex, который игнорируется. Этим надо воспользоваться для обработки pre и code
373
+
374
+ @glueleft = ['рис.', 'табл.', 'см.', 'им.', 'ул.', 'пер.', 'кв.', 'офис', 'оф.', 'г.']
375
+ @glueright = ['руб.', 'коп.', 'у.е.', 'мин.']
376
+
377
+ # Установки можно менять в каждом экземпляре
378
+ @settings = SETTINGS.dup
379
+
380
+ @mark_tag = REPLACEMENT_MARKER
381
+ # Глифы можено подменять в экземпляре форматтера поэтому копируем их из константы
382
+ @glyph = GLYPHS.dup
383
+
384
+ @phonemasks = [[ /([0-9]{4})\-([0-9]{2})\-([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})/,
385
+ /([0-9]{4})\-([0-9]{2})\-([0-9]{2})/,
386
+ /(\([0-9\+\-]+\)) ?([0-9]{3})\-([0-9]{2})\-([0-9]{2})/,
387
+ /(\([0-9\+\-]+\)) ?([0-9]{2})\-([0-9]{2})\-([0-9]{2})/,
388
+ /(\([0-9\+\-]+\)) ?([0-9]{3})\-([0-9]{2})/,
389
+ /(\([0-9\+\-]+\)) ?([0-9]{2})\-([0-9]{3})/,
390
+ /([0-9]{3})\-([0-9]{2})\-([0-9]{2})/,
391
+ /([0-9]{2})\-([0-9]{2})\-([0-9]{2})/,
392
+ /([0-9]{1})\-([0-9]{2})\-([0-9]{2})/,
393
+ /([0-9]{2})\-([0-9]{3})/,
394
+ /([0-9]+)\-([0-9]+)/,
395
+ ],[
396
+ ':nob_open\1:ndash\2:ndash\3:nbsp\4:\5:\6:nob_close',
397
+ ':nob_open\1:ndash\2:ndash\3:nob_close',
398
+ ':nob_open\1:nbsp\2:ndash\3:ndash\4:nob_close',
399
+ ':nob_open\1:nbsp\2:ndash\3:ndash\4:nob_close',
400
+ ':nob_open\1:nbsp\2:ndash\3:nob_close',
401
+ ':nob_open\1:nbsp\2:ndash\3:nob_close',
402
+ ':nob_open\1:ndash\2:ndash\3:nob_close',
403
+ ':nob_open\1:ndash\2:ndash\3:nob_close',
404
+ ':nob_open\1:ndash\2:ndash\3:nob_close',
405
+ ':nob_open\1:ndash\2:nob_close',
406
+ ':nob_open\1:ndash\2:nob_close'
407
+ ]]
408
+ end
409
+
410
+ # Позволяет получить процедуру, при вызове возвращающую значение глифа
411
+ # def lookup(glyph_to_lookup)
412
+ # return Proc.new { g[glyph_to_lookup] }
413
+ # end
414
+
415
+ # Подставляет "символы" (двоеточие + имя глифа) на нужное значение глифа заданное в данном форматтере
416
+ def substitute_glyphs_in_string(str)
417
+ re = str.dup
418
+ @glyph.each_pair do | key, subst |
419
+ re.gsub!(":#{key.to_s}", subst)
420
+ end
421
+ re
422
+ end
423
+
424
+ # Выполняет блок, временно включая настройки переданные в +hash+
425
+ def with_configuration(hash, &block)
426
+ old_settings, old_glyphs = @settings.dup, @glyph.dup
427
+ accept_configuration_arguments!(hash)
428
+ txt = yield
429
+ @settings, @glyph = old_settings, old_glyphs
430
+
431
+ return txt
432
+ end
433
+
434
+ def accept_configuration_arguments!(args_hash)
435
+
436
+ # Специальный случай - :all=>true|false
437
+ if args_hash.has_key?(:all)
438
+ if args_hash[:all]
439
+ @settings.each_pair {|k, v| @settings[k] = true unless PROTECTED_SETTINGS.include?(k.to_sym)}
440
+ else
441
+ @settings.each_pair {|k, v| @settings[k] = false unless PROTECTED_SETTINGS.include?(k.to_sym)}
442
+ end
443
+ else
444
+
445
+ # Кинуть ошибку если настройка нам неизвестна
446
+ unknown_settings = args_hash.keys.collect{|k|k.to_s} - @settings.keys.collect { |k| k.to_s }
447
+ raise RuTils::Gilenson::UnknownSetting, unknown_settings if unknown_settings.any?
448
+
449
+ args_hash.each_pair do | key, value |
450
+ @settings[key.to_s] = (value ? true : false)
451
+ end
452
+ end
453
+ end
454
+
455
+ # Вынимает игнорируемые фрагменты и заменяет их маркером, выполняет переданный блок и вставляет вынутое на место
456
+ def lifting_fragments(text, &block)
457
+ lifted = lift_ignored_elements(text)
458
+ yield
459
+ reinsert_fragments(text, lifted)
460
+ end
461
+
462
+ #Вынимает фрагменты из текста и возвращает массив с фрагментами
463
+ def lift_ignored_elements(text)
464
+ # re = /<\/?[a-z0-9]+("+ # имя тага
465
+ # "\s+("+ # повторяющая конструкция: хотя бы один разделитель и тельце
466
+ # "[a-z]+("+ # атрибут из букв, за которым может стоять знак равенства и потом
467
+ # "=((\'[^\']*\')|(\"[^\"]*\")|([0-9@\-_a-z:\/?&=\.]+))"+ #
468
+ # ")?"+
469
+ # ")?"+
470
+ # ")*\/?>|\xA2\xA2[^\n]*?==/i;
471
+
472
+ re_skipcode = '((<(code|tt)[ >](.*?)<\/(code|tt)>)|(<!\[CDATA\[(.*?)\]\]>))|' if @settings['skip_code']
473
+ re = /(#{re_skipcode}<\/?[a-z0-9]+(\s+([a-z]+(=((\'[^\']*\')|(\"[^\"]*\")|([0-9@\-_a-z:\/?&=\.]+)))?)?)*\/?>)/uim
474
+ tags = text.scan(re).map{ |tag| tag[0] } # первая группа!
475
+ text.gsub!(re, @mark_tag) #маркер тега, мы используем Invalid UTF-sequence для него
476
+ return tags
477
+ end
478
+
479
+ def reinsert_fragments(text, fragments)
480
+ fragments.each do |fragment|
481
+ fragment.gsub!(/ (href|src|data)=((?:(\')([^\']*)(\'))|(?:(\")([^\"]*)(\")))/uim) do
482
+ " #{$1}=" + $2.gsub(/&(?!(#0*38)|(amp);)/, @amp)
483
+ end # unless @settings['raw_output'] -- делать это надо всегда (mash)
484
+
485
+ unless @settings['skip_attr']
486
+ fragment.gsub!(/ (title|alt)=((?:(\')([^\']*)(\'))|(?:(\")([^\"]*)(\")))/uim) do
487
+ " #{$1}=#{$3}" + self.process($4.to_s) + "#{$5}#{$6}" + self.process($7.to_s) + "#{$8}"
488
+ end
489
+ end
490
+ text.sub!(@mark_tag, fragment)
491
+ end
492
+ end
493
+
494
+ ### Имплементации фильтров
495
+ def process_html_entities(text)
496
+ self.glyph.each { |key, value| text.gsub!(/&#{key};/, value)}
497
+ end
498
+
499
+ def process_initials(text)
500
+ initials = /([А-Я])[\.]{1,2}[\s]*?([А-Я])[\.]*[\s]*?([А-Я])([а-я])/u
501
+ replacement = substitute_glyphs_in_string('\1.\2.:thinsp\3\4')
502
+ text.gsub!(initials, replacement)
503
+ end
504
+
505
+ def process_copy_paste_clearing(text)
506
+ VERBATIM_GLYPHS.each {|key,value| text.gsub!(/#{Regexp.escape(key)}/, glyph[value]) }
507
+ end
508
+
509
+ def process_spacing(text)
510
+ text.gsub!( /(\s*)([,]*)/sui, '\2\1');
511
+ text.gsub!( /(\s*)([\.?!]*)(\s*[ЁА-ЯA-Z])/su, '\2\1\3');
512
+ end
513
+
514
+ def process_dashglue(text)
515
+ text.gsub!( /([a-zа-яА-Я0-9]+(\-[a-zа-яА-Я0-9]+)+)/ui, @nob_open+'\1'+ @nob_close)
516
+ end
517
+
518
+ def process_escape_html(text)
519
+ text.gsub!(/&/, @amp)
520
+ text.gsub!(/</, @lt)
521
+ text.gsub!(/>/, @gt)
522
+ end
523
+
524
+ def process_span_instead_of_nobr(text)
525
+ text.gsub!(/<nobr>/, '<span class="nobr">')
526
+ text.gsub!(/<\/nobr>/, '</span>')
527
+ end
528
+
529
+ def process_dash(text)
530
+ text.gsub!( /(\s|;)\-(\s)/ui, '\1'+@ndash+'\2')
531
+ end
532
+
533
+ def process_emdash(text)
534
+ text.gsub!( /(\s|;)\-\-(\s)/ui, '\1'+@mdash+'\2')
535
+ # 4. (с)
536
+ text.gsub!(/\([сСcC]\)((?=\w)|(?=\s[0-9]+))/u, @copy) if @settings["(c)"]
537
+ # 4a. (r)
538
+ text.gsub!( /\(r\)/ui, '<sup>'+@reg+'</sup>') if @settings["(r)"]
539
+
540
+ # 4b. (tm)
541
+ text.gsub!( /\(tm\)|\(тм\)/ui, @trade) if @settings["(tm)"]
542
+ # 4c. (p)
543
+ text.gsub!( /\(p\)/ui, @sect) if @settings["(p)"]
544
+ end
545
+
546
+ def process_ellipsises(text)
547
+ text.gsub!( '...', @hellip)
548
+ end
549
+
550
+ def process_laquo(text)
551
+ text.gsub!( /\"\"/ui, @quot * 2);
552
+ text.gsub!( /(^|\s|#{@mark_tag}|>|\()\"((#{@mark_tag})*[~0-9ёЁA-Za-zА-Яа-я\-:\/\.])/ui, '\1' + @laquo + '\2');
553
+ _text = '""';
554
+ until _text == text do
555
+ _text = text;
556
+ text.gsub!( /(#{@laquo}([^\"]*)[ёЁA-Za-zА-Яа-я0-9\.\-:\/\?\!](#{@mark_tag})*)\"/sui, '\1' + @raquo)
557
+ end
558
+ end
559
+
560
+ def process_quotes(text)
561
+ text.gsub!( /\"\"/ui, @quot*2)
562
+ text.gsub!( /\"\.\"/ui, @quot+"."+@quot)
563
+ _text = '""';
564
+ lat_c = '0-9A-Za-z'
565
+ punct = /\'\!\s\.\?\,\-\&\;\:\\/
566
+
567
+ until _text == text do
568
+ _text = text.dup
569
+ text.gsub!( /(^|\s|#{@mark_tag}|>)\"([#{lat_c}#{punct}\_\#{@mark_tag}]+(\"|#{@rdquo}))/ui, '\1'+ @ldquo +'\2')
570
+ text.gsub!( /(#{@ldquo}([#{lat_c}#{punct}#{@mark_tag}\_]*).*[#{lat_c}][\#{@mark_tag}\?\.\!\,\\]*)\"/ui, '\1'+ @rdquo)
571
+ end
572
+ end
573
+
574
+ def process_compound_quotes(text)
575
+ text.gsub!(/(#{@ldquo}(([A-Za-z0-9'!\.?,\-&;:]|\s|#{@mark_tag})*)#{@laquo}(.*)#{@raquo})#{@raquo}/ui, '\1' + @rdquo);
576
+ end
577
+
578
+ def process_degrees(text)
579
+ text.gsub!( /-([0-9])+\^([FCС])/, @ndash+'\1'+ @deg +'\2') #deg
580
+ text.gsub!( /\+([0-9])+\^([FCС])/, '+\1'+ @deg +'\2')
581
+ text.gsub!( /\^([FCС])/, @deg+'\1')
582
+ end
583
+
584
+ def process_wordglue(text)
585
+ text.replace(" " + text + " ")
586
+ _text = " " + text + " "
587
+
588
+ until _text == text
589
+ _text = text
590
+ text.gsub!( /(\s+)([a-zа-яА-Я0-9]{1,2})(\s+)([^\\s$])/ui, '\1\2' + @nbsp +'\4')
591
+ text.gsub!( /(\s+)([a-zа-яА-Я0-9]{3})(\s+)([^\\s$])/ui, '\1\2' + @nbsp+'\4')
592
+ end
593
+
594
+ # Пунктуация это либо один из наших глифов, либо мемберы класса. В данном случае
595
+ # мы цепляемся за кончик строки поэтому можум прихватить и глиф тоже
596
+ # Пунктуация включает наши собственные глифы!
597
+ punct = glyph.values.map{|v| Regexp.escape(v)}.join('|')
598
+ vpunct = /(#{punct}|[\)\]\!\?,\.;])/
599
+
600
+ text.gsub!(/(\s+)([a-zа-яА-Я0-9]{1,2}#{vpunct}{0,3}\s$)/ui, @nbsp+'\2')
601
+
602
+ @glueleft.each { | i | text.gsub!( /(\s)(#{i})(\s+)/sui, '\1\2' + @nbsp) }
603
+
604
+ @glueright.each { | i | text.gsub!( /(\s)(#{i})(\s+)/sui, @nbsp+'\2\3') }
605
+
606
+ end
607
+
608
+ def process_phones(text)
609
+ @phonemasks[0].each_with_index do |pattern, i|
610
+ replacement = substitute_glyphs_in_string(@phonemasks[1][i])
611
+ text.gsub!(pattern, replacement)
612
+ end
613
+ end
614
+
615
+ def process_acronyms(text)
616
+ acronym = /\b([A-ZА-Я][A-ZА-Я0-9]{2,})\b(?:[(]([^)]*)[)])/u
617
+ if @settings["raw_output"]
618
+ text.gsub!(acronym, '\1%s(\2)' % @thinsp)
619
+ else
620
+ text.gsub!(acronym) do
621
+ expl = $2.to_s; process_escape_html(expl)
622
+ "<acronym title=\"#{expl}\">#{$1}</acronym>"
623
+ end
624
+ end
625
+ end
626
+
627
+ # Обработка знака дюйма, кроме случаев когда он внутри кавычек
628
+ def process_inches(text)
629
+ text.gsub!(/\s([0-9]{1,2}([\.,][0-9]{1,2})?)(\"){1,1}/ui, ' \1' + @inch)
630
+ end
631
+
632
+ def process_plusmin(text)
633
+ text.gsub!(/[^+]\+\-/ui, @plusmn)
634
+ end
635
+
636
+ # Подменяет все юникодные entities в тексте на истинные UTF-8-символы
637
+ def process_raw_output(text)
638
+ # Все глифы
639
+ @glyph.values.each do | entity |
640
+ next unless entity =~ /^&#(\d+);/
641
+ text.gsub!(/#{entity}/, entity_to_raw_utf8(entity))
642
+ end
643
+ end
644
+
645
+ # Конвертирует юникодные entities в UTF-8-codepoints
646
+ def entity_to_raw_utf8(entity)
647
+ entity =~ /^&#(\d+);/
648
+ $1 ? [$1.to_i].pack("U") : entity
649
+ end
650
+
651
+ end #end Gilenson
652
+
653
+ # Выбрасывается если форматтеру задается неизвестная настройка
654
+ class RuTils::Gilenson::UnknownSetting < RuntimeError
655
+ end
656
+
657
+ module RuTils::Gilenson::StringFormatting
658
+ # Форматирует строку с помощью Gilenson::Formatter. Все дополнительные опции передаются форматтеру.
659
+ def gilensize(*args)
660
+ RuTils::Gilenson::Formatter.new(self, args.shift || {}).to_html
661
+ end
662
+
663
+ end
664
+
665
+ Object::String.send(:include, RuTils::Gilenson::StringFormatting)